xref: /haiku/src/kits/interface/ListView.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
1 /*
2  * Copyright 2001-2015 Haiku, Inc. All rights resrerved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Stephan Assmus, superstippi@gmx.de
7  *		Axel Dörfler, axeld@pinc-software.de
8  *		Marc Flerackers, mflerackers@androme.be
9  *		Rene Gollent, rene@gollent.com
10  *		Ulrich Wimboeck
11  */
12 
13 
14 #include <ListView.h>
15 
16 #include <algorithm>
17 
18 #include <stdio.h>
19 
20 #include <Autolock.h>
21 #include <LayoutUtils.h>
22 #include <PropertyInfo.h>
23 #include <ScrollBar.h>
24 #include <ScrollView.h>
25 #include <Thread.h>
26 #include <Window.h>
27 
28 #include <binary_compatibility/Interface.h>
29 
30 
31 struct track_data {
32 	BPoint		drag_start;
33 	int32		item_index;
34 	bool		was_selected;
35 	bool		try_drag;
36 	bool		is_dragging;
37 	bigtime_t	last_click_time;
38 };
39 
40 
41 const float kDoubleClickThreshold = 6.0f;
42 
43 
44 static property_info sProperties[] = {
45 	{ "Item", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
46 		"Returns the number of BListItems currently in the list.", 0,
47 		{ B_INT32_TYPE }
48 	},
49 
50 	{ "Item", { B_EXECUTE_PROPERTY, 0 }, { B_INDEX_SPECIFIER,
51 		B_REVERSE_INDEX_SPECIFIER, B_RANGE_SPECIFIER,
52 		B_REVERSE_RANGE_SPECIFIER, 0 },
53 		"Select and invoke the specified items, first removing any existing "
54 		"selection."
55 	},
56 
57 	{ "Selection", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
58 		"Returns int32 count of items in the selection.", 0, { B_INT32_TYPE }
59 	},
60 
61 	{ "Selection", { B_EXECUTE_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
62 		"Invoke items in selection."
63 	},
64 
65 	{ "Selection", { B_GET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
66 		"Returns int32 indices of all items in the selection.", 0,
67 		{ B_INT32_TYPE }
68 	},
69 
70 	{ "Selection", { B_SET_PROPERTY, 0 }, { B_INDEX_SPECIFIER,
71 		B_REVERSE_INDEX_SPECIFIER, B_RANGE_SPECIFIER,
72 		B_REVERSE_RANGE_SPECIFIER, 0 },
73 		"Extends current selection or deselects specified items. Boolean field "
74 		"\"data\" chooses selection or deselection.", 0, { B_BOOL_TYPE }
75 	},
76 
77 	{ "Selection", { B_SET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
78 		"Select or deselect all items in the selection. Boolean field \"data\" "
79 		"chooses selection or deselection.", 0, { B_BOOL_TYPE }
80 	},
81 
82 	{ 0 }
83 };
84 
85 
86 BListView::BListView(BRect frame, const char* name, list_view_type type,
87 	uint32 resizingMode, uint32 flags)
88 	:
89 	BView(frame, name, resizingMode, flags | B_SCROLL_VIEW_AWARE)
90 {
91 	_InitObject(type);
92 }
93 
94 
95 BListView::BListView(const char* name, list_view_type type, uint32 flags)
96 	:
97 	BView(name, flags | B_SCROLL_VIEW_AWARE)
98 {
99 	_InitObject(type);
100 }
101 
102 
103 BListView::BListView(list_view_type type)
104 	:
105 	BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE
106 		| B_SCROLL_VIEW_AWARE)
107 {
108 	_InitObject(type);
109 }
110 
111 
112 BListView::BListView(BMessage* archive)
113 	:
114 	BView(archive)
115 {
116 	int32 listType;
117 	archive->FindInt32("_lv_type", &listType);
118 	_InitObject((list_view_type)listType);
119 
120 	int32 i = 0;
121 	BMessage subData;
122 	while (archive->FindMessage("_l_items", i++, &subData) == B_OK) {
123 		BArchivable* object = instantiate_object(&subData);
124 		if (object == NULL)
125 			continue;
126 
127 		BListItem* item = dynamic_cast<BListItem*>(object);
128 		if (item != NULL)
129 			AddItem(item);
130 	}
131 
132 	if (archive->HasMessage("_msg")) {
133 		BMessage* invokationMessage = new BMessage;
134 
135 		archive->FindMessage("_msg", invokationMessage);
136 		SetInvocationMessage(invokationMessage);
137 	}
138 
139 	if (archive->HasMessage("_2nd_msg")) {
140 		BMessage* selectionMessage = new BMessage;
141 
142 		archive->FindMessage("_2nd_msg", selectionMessage);
143 		SetSelectionMessage(selectionMessage);
144 	}
145 }
146 
147 
148 BListView::~BListView()
149 {
150 	// NOTE: According to BeBook, BListView does not free the items itself.
151 	delete fTrack;
152 	SetSelectionMessage(NULL);
153 }
154 
155 
156 // #pragma mark -
157 
158 
159 BArchivable*
160 BListView::Instantiate(BMessage* archive)
161 {
162 	if (validate_instantiation(archive, "BListView"))
163 		return new BListView(archive);
164 
165 	return NULL;
166 }
167 
168 
169 status_t
170 BListView::Archive(BMessage* data, bool deep) const
171 {
172 	status_t status = BView::Archive(data, deep);
173 	if (status < B_OK)
174 		return status;
175 
176 	status = data->AddInt32("_lv_type", fListType);
177 	if (status == B_OK && deep) {
178 		BListItem* item;
179 		int32 i = 0;
180 
181 		while ((item = ItemAt(i++)) != NULL) {
182 			BMessage subData;
183 			status = item->Archive(&subData, true);
184 			if (status >= B_OK)
185 				status = data->AddMessage("_l_items", &subData);
186 
187 			if (status < B_OK)
188 				break;
189 		}
190 	}
191 
192 	if (status >= B_OK && InvocationMessage() != NULL)
193 		status = data->AddMessage("_msg", InvocationMessage());
194 
195 	if (status == B_OK && fSelectMessage != NULL)
196 		status = data->AddMessage("_2nd_msg", fSelectMessage);
197 
198 	return status;
199 }
200 
201 
202 // #pragma mark -
203 
204 
205 void
206 BListView::Draw(BRect updateRect)
207 {
208 	int32 count = CountItems();
209 	if (count == 0)
210 		return;
211 
212 	BRect itemFrame(0, 0, Bounds().right, -1);
213 	for (int i = 0; i < count; i++) {
214 		BListItem* item = ItemAt(i);
215 		itemFrame.bottom = itemFrame.top + ceilf(item->Height()) - 1;
216 
217 		if (itemFrame.Intersects(updateRect))
218 			DrawItem(item, itemFrame);
219 
220 		itemFrame.top = itemFrame.bottom + 1;
221 	}
222 }
223 
224 
225 void
226 BListView::AttachedToWindow()
227 {
228 	BView::AttachedToWindow();
229 	_UpdateItems();
230 
231 	if (!Messenger().IsValid())
232 		SetTarget(Window(), NULL);
233 
234 	_FixupScrollBar();
235 }
236 
237 
238 void
239 BListView::DetachedFromWindow()
240 {
241 	BView::DetachedFromWindow();
242 }
243 
244 
245 void
246 BListView::AllAttached()
247 {
248 	BView::AllAttached();
249 }
250 
251 
252 void
253 BListView::AllDetached()
254 {
255 	BView::AllDetached();
256 }
257 
258 
259 void
260 BListView::FrameResized(float newWidth, float newHeight)
261 {
262 	_FixupScrollBar();
263 
264 	// notify items of new width.
265 	_UpdateItems();
266 }
267 
268 
269 void
270 BListView::FrameMoved(BPoint newPosition)
271 {
272 	BView::FrameMoved(newPosition);
273 }
274 
275 
276 void
277 BListView::TargetedByScrollView(BScrollView* view)
278 {
279 	fScrollView = view;
280 	// TODO: We could SetFlags(Flags() | B_FRAME_EVENTS) here, but that
281 	// may mess up application code which manages this by some other means
282 	// and doesn't want us to be messing with flags.
283 }
284 
285 
286 void
287 BListView::WindowActivated(bool active)
288 {
289 	BView::WindowActivated(active);
290 }
291 
292 
293 // #pragma mark -
294 
295 
296 void
297 BListView::MessageReceived(BMessage* message)
298 {
299 	switch (message->what) {
300 		case B_MOUSE_WHEEL_CHANGED:
301 			if (!fTrack->is_dragging)
302 				BView::MessageReceived(message);
303 			break;
304 
305 		case B_COUNT_PROPERTIES:
306 		case B_EXECUTE_PROPERTY:
307 		case B_GET_PROPERTY:
308 		case B_SET_PROPERTY:
309 		{
310 			BPropertyInfo propInfo(sProperties);
311 			BMessage specifier;
312 			const char* property;
313 
314 			if (message->GetCurrentSpecifier(NULL, &specifier) != B_OK
315 				|| specifier.FindString("property", &property) != B_OK) {
316 				return;
317 			}
318 
319 			switch (propInfo.FindMatch(message, 0, &specifier, message->what,
320 					property)) {
321 				case B_ERROR:
322 					BView::MessageReceived(message);
323 					break;
324 
325 				case 0:
326 				{
327 					BMessage reply(B_REPLY);
328 					reply.AddInt32("result", CountItems());
329 					reply.AddInt32("error", B_OK);
330 
331 					message->SendReply(&reply);
332 					break;
333 				}
334 
335 				case 1:
336 					break;
337 
338 				case 2:
339 				{
340 					int32 count = 0;
341 
342 					for (int32 i = 0; i < CountItems(); i++) {
343 						if (ItemAt(i)->IsSelected())
344 							count++;
345 					}
346 
347 					BMessage reply(B_REPLY);
348 					reply.AddInt32("result", count);
349 					reply.AddInt32("error", B_OK);
350 
351 					message->SendReply(&reply);
352 					break;
353 				}
354 
355 				case 3:
356 					break;
357 
358 				case 4:
359 				{
360 					BMessage reply (B_REPLY);
361 
362 					for (int32 i = 0; i < CountItems(); i++) {
363 						if (ItemAt(i)->IsSelected())
364 							reply.AddInt32("result", i);
365 					}
366 
367 					reply.AddInt32("error", B_OK);
368 
369 					message->SendReply(&reply);
370 					break;
371 				}
372 
373 				case 5:
374 					break;
375 
376 				case 6:
377 				{
378 					BMessage reply(B_REPLY);
379 
380 					bool select;
381 					if (message->FindBool("data", &select) == B_OK && select)
382 						Select(0, CountItems() - 1, false);
383 					else
384 						DeselectAll();
385 
386 					reply.AddInt32("error", B_OK);
387 
388 					message->SendReply(&reply);
389 					break;
390 				}
391 			}
392 			break;
393 		}
394 
395 		case B_SELECT_ALL:
396 			if (fListType == B_MULTIPLE_SELECTION_LIST)
397 				Select(0, CountItems() - 1, false);
398 			break;
399 
400 		default:
401 			BView::MessageReceived(message);
402 	}
403 }
404 
405 
406 void
407 BListView::KeyDown(const char* bytes, int32 numBytes)
408 {
409 	bool extend = fListType == B_MULTIPLE_SELECTION_LIST
410 		&& (modifiers() & B_SHIFT_KEY) != 0;
411 
412 	if (fFirstSelected == -1
413 		&& (bytes[0] == B_UP_ARROW || bytes[0] == B_DOWN_ARROW)) {
414 		// nothing is selected yet, select the first enabled item
415 		int32 lastItem = CountItems() - 1;
416 		for (int32 i = 0; i <= lastItem; i++) {
417 			if (ItemAt(i)->IsEnabled()) {
418 				Select(i);
419 				break;
420 			}
421 		}
422 		return;
423 	}
424 
425 	switch (bytes[0]) {
426 		case B_UP_ARROW:
427 		{
428 			if (fAnchorIndex > 0) {
429 				if (!extend || fAnchorIndex <= fFirstSelected) {
430 					for (int32 i = 1; fAnchorIndex - i >= 0; i++) {
431 						if (ItemAt(fAnchorIndex - i)->IsEnabled()) {
432 							// Select the previous enabled item
433 							Select(fAnchorIndex - i, extend);
434 							break;
435 						}
436 					}
437 				} else {
438 					Deselect(fAnchorIndex);
439 					do
440 						fAnchorIndex--;
441 					while (fAnchorIndex > 0
442 						&& !ItemAt(fAnchorIndex)->IsEnabled());
443 				}
444 			}
445 
446 			ScrollToSelection();
447 			break;
448 		}
449 
450 		case B_DOWN_ARROW:
451 		{
452 			int32 lastItem = CountItems() - 1;
453 			if (fAnchorIndex < lastItem) {
454 				if (!extend || fAnchorIndex >= fLastSelected) {
455 					for (int32 i = 1; fAnchorIndex + i <= lastItem; i++) {
456 						if (ItemAt(fAnchorIndex + i)->IsEnabled()) {
457 							// Select the next enabled item
458 							Select(fAnchorIndex + i, extend);
459 							break;
460 						}
461 					}
462 				} else {
463 					Deselect(fAnchorIndex);
464 					do
465 						fAnchorIndex++;
466 					while (fAnchorIndex < lastItem
467 						&& !ItemAt(fAnchorIndex)->IsEnabled());
468 				}
469 			}
470 
471 			ScrollToSelection();
472 			break;
473 		}
474 
475 		case B_HOME:
476 			if (extend) {
477 				Select(0, fAnchorIndex, true);
478 				fAnchorIndex = 0;
479 			} else {
480 				// select the first enabled item
481 				int32 lastItem = CountItems() - 1;
482 				for (int32 i = 0; i <= lastItem; i++) {
483 					if (ItemAt(i)->IsEnabled()) {
484 						Select(i, false);
485 						break;
486 					}
487 				}
488 			}
489 
490 			ScrollToSelection();
491 			break;
492 
493 		case B_END:
494 			if (extend) {
495 				Select(fAnchorIndex, CountItems() - 1, true);
496 				fAnchorIndex = CountItems() - 1;
497 			} else {
498 				// select the last enabled item
499 				for (int32 i = CountItems() - 1; i >= 0; i--) {
500 					if (ItemAt(i)->IsEnabled()) {
501 						Select(i, false);
502 						break;
503 					}
504 				}
505 			}
506 
507 			ScrollToSelection();
508 			break;
509 
510 		case B_PAGE_UP:
511 		{
512 			BPoint scrollOffset(LeftTop());
513 			scrollOffset.y = std::max(0.0f, scrollOffset.y - Bounds().Height());
514 			ScrollTo(scrollOffset);
515 			break;
516 		}
517 
518 		case B_PAGE_DOWN:
519 		{
520 			BPoint scrollOffset(LeftTop());
521 			if (BListItem* item = LastItem()) {
522 				scrollOffset.y += Bounds().Height();
523 				scrollOffset.y = std::min(item->Bottom() - Bounds().Height(),
524 					scrollOffset.y);
525 			}
526 			ScrollTo(scrollOffset);
527 			break;
528 		}
529 
530 		case B_RETURN:
531 		case B_SPACE:
532 			Invoke();
533 			break;
534 
535 		default:
536 			BView::KeyDown(bytes, numBytes);
537 	}
538 }
539 
540 
541 void
542 BListView::MouseDown(BPoint where)
543 {
544 	if (!IsFocus()) {
545 		MakeFocus();
546 		Sync();
547 		Window()->UpdateIfNeeded();
548 	}
549 
550 	int32 index = IndexOf(where);
551 	int32 modifiers = 0;
552 
553 	BMessage* message = Looper()->CurrentMessage();
554 	if (message != NULL)
555 		message->FindInt32("modifiers", &modifiers);
556 
557 	// If the user double (or more) clicked within the current selection,
558 	// we don't change the selection but invoke the selection.
559 	// TODO: move this code someplace where it can be shared everywhere
560 	// instead of every class having to reimplement it, once some sane
561 	// API for it is decided.
562 	BPoint delta = where - fTrack->drag_start;
563 	bigtime_t sysTime;
564 	Window()->CurrentMessage()->FindInt64("when", &sysTime);
565 	bigtime_t timeDelta = sysTime - fTrack->last_click_time;
566 	bigtime_t doubleClickSpeed;
567 	get_click_speed(&doubleClickSpeed);
568 	bool doubleClick = false;
569 
570 	if (timeDelta < doubleClickSpeed
571 		&& fabs(delta.x) < kDoubleClickThreshold
572 		&& fabs(delta.y) < kDoubleClickThreshold
573 		&& fTrack->item_index == index) {
574 		doubleClick = true;
575 	}
576 
577 	if (doubleClick && index >= fFirstSelected && index <= fLastSelected) {
578 		fTrack->drag_start.Set(INT32_MAX, INT32_MAX);
579 		Invoke();
580 		return BView::MouseDown(where);
581 	}
582 
583 	if (!doubleClick) {
584 		fTrack->drag_start = where;
585 		fTrack->last_click_time = system_time();
586 		fTrack->item_index = index;
587 		fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false;
588 		fTrack->try_drag = true;
589 
590 		MouseDownThread<BListView>::TrackMouse(this,
591 			&BListView::_DoneTracking, &BListView::_Track);
592 	}
593 
594 	if (index >= 0) {
595 		if (fListType == B_MULTIPLE_SELECTION_LIST) {
596 			if ((modifiers & B_SHIFT_KEY) != 0) {
597 				// select entire block
598 				if (index >= fFirstSelected && index < fLastSelected)
599 					// clicked inside of selected items block, deselect all
600 					// but from the first selected item to the clicked item
601 					DeselectExcept(fFirstSelected, index);
602 				else
603 					Select(std::min(index, fFirstSelected), std::max(index,
604 						fLastSelected));
605 			} else {
606 				if ((modifiers & B_COMMAND_KEY) != 0) {
607 					// toggle selection state of clicked item (like in Tracker)
608 					if (ItemAt(index)->IsSelected())
609 						Deselect(index);
610 					else
611 						Select(index, true);
612 				} else if (!ItemAt(index)->IsSelected())
613 					// To enable multi-select drag and drop, we only
614 					// exclusively select a single item if it's not one of the
615 					// already selected items. This behavior gives the mouse
616 					// tracking thread the opportunity to initiate the
617 					// multi-selection drag with all the items still selected.
618 					Select(index);
619 			}
620 		} else {
621 			// toggle selection state of clicked item (except drag & drop)
622 			if ((modifiers & B_COMMAND_KEY) != 0 && ItemAt(index)->IsSelected())
623 				Deselect(index);
624 			else
625 				Select(index);
626 		}
627 	} else if ((modifiers & B_COMMAND_KEY) == 0)
628 		DeselectAll();
629 
630 	BView::MouseDown(where);
631 }
632 
633 
634 void
635 BListView::MouseUp(BPoint where)
636 {
637 	BView::MouseUp(where);
638 }
639 
640 
641 void
642 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
643 {
644 	BView::MouseMoved(where, code, dragMessage);
645 }
646 
647 
648 bool
649 BListView::InitiateDrag(BPoint where, int32 index, bool wasSelected)
650 {
651 	return false;
652 }
653 
654 
655 // #pragma mark -
656 
657 
658 void
659 BListView::ResizeToPreferred()
660 {
661 	BView::ResizeToPreferred();
662 }
663 
664 
665 void
666 BListView::GetPreferredSize(float *_width, float *_height)
667 {
668 	int32 count = CountItems();
669 
670 	if (count > 0) {
671 		float maxWidth = 0.0;
672 		for (int32 i = 0; i < count; i++) {
673 			float itemWidth = ItemAt(i)->Width();
674 			if (itemWidth > maxWidth)
675 				maxWidth = itemWidth;
676 		}
677 
678 		if (_width != NULL)
679 			*_width = maxWidth;
680 		if (_height != NULL)
681 			*_height = ItemAt(count - 1)->Bottom();
682 	} else
683 		BView::GetPreferredSize(_width, _height);
684 }
685 
686 
687 BSize
688 BListView::MinSize()
689 {
690 	// We need a stable min size: the BView implementation uses
691 	// GetPreferredSize(), which by default just returns the current size.
692 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
693 }
694 
695 
696 BSize
697 BListView::MaxSize()
698 {
699 	return BView::MaxSize();
700 }
701 
702 
703 BSize
704 BListView::PreferredSize()
705 {
706 	// We need a stable preferred size: the BView implementation uses
707 	// GetPreferredSize(), which by default just returns the current size.
708 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
709 }
710 
711 
712 // #pragma mark -
713 
714 
715 void
716 BListView::MakeFocus(bool focused)
717 {
718 	if (IsFocus() == focused)
719 		return;
720 
721 	BView::MakeFocus(focused);
722 
723 	if (fScrollView)
724 		fScrollView->SetBorderHighlighted(focused);
725 }
726 
727 
728 void
729 BListView::SetFont(const BFont* font, uint32 mask)
730 {
731 	BView::SetFont(font, mask);
732 
733 	if (Window() != NULL && !Window()->InViewTransaction())
734 		_UpdateItems();
735 }
736 
737 
738 void
739 BListView::ScrollTo(BPoint point)
740 {
741 	BView::ScrollTo(point);
742 }
743 
744 
745 // #pragma mark - List ops
746 
747 
748 bool
749 BListView::AddItem(BListItem* item, int32 index)
750 {
751 	if (!fList.AddItem(item, index))
752 		return false;
753 
754 	if (fFirstSelected != -1 && index <= fFirstSelected)
755 		fFirstSelected++;
756 
757 	if (fLastSelected != -1 && index <= fLastSelected)
758 		fLastSelected++;
759 
760 	if (Window()) {
761 		BFont font;
762 		GetFont(&font);
763 		item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
764 
765 		item->Update(this, &font);
766 		_RecalcItemTops(index + 1);
767 
768 		_FixupScrollBar();
769 		_InvalidateFrom(index);
770 	}
771 
772 	return true;
773 }
774 
775 
776 bool
777 BListView::AddItem(BListItem* item)
778 {
779 	if (!fList.AddItem(item))
780 		return false;
781 
782 	// No need to adapt selection, as this item is the last in the list
783 
784 	if (Window()) {
785 		BFont font;
786 		GetFont(&font);
787 		int32 index = CountItems() - 1;
788 		item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
789 
790 		item->Update(this, &font);
791 
792 		_FixupScrollBar();
793 		InvalidateItem(CountItems() - 1);
794 	}
795 
796 	return true;
797 }
798 
799 
800 bool
801 BListView::AddList(BList* list, int32 index)
802 {
803 	if (!fList.AddList(list, index))
804 		return false;
805 
806 	int32 count = list->CountItems();
807 
808 	if (fFirstSelected != -1 && index < fFirstSelected)
809 		fFirstSelected += count;
810 
811 	if (fLastSelected != -1 && index < fLastSelected)
812 		fLastSelected += count;
813 
814 	if (Window()) {
815 		BFont font;
816 		GetFont(&font);
817 
818 		for (int32 i = index; i <= (index + count - 1); i++) {
819 			ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
820 			ItemAt(i)->Update(this, &font);
821 		}
822 
823 		_RecalcItemTops(index + count - 1);
824 
825 		_FixupScrollBar();
826 		Invalidate(); // TODO
827 	}
828 
829 	return true;
830 }
831 
832 
833 bool
834 BListView::AddList(BList* list)
835 {
836 	return AddList(list, CountItems());
837 }
838 
839 
840 BListItem*
841 BListView::RemoveItem(int32 index)
842 {
843 	BListItem* item = ItemAt(index);
844 	if (item == NULL)
845 		return NULL;
846 
847 	if (item->IsSelected())
848 		Deselect(index);
849 
850 	if (!fList.RemoveItem(item))
851 		return NULL;
852 
853 	if (fFirstSelected != -1 && index < fFirstSelected)
854 		fFirstSelected--;
855 
856 	if (fLastSelected != -1 && index < fLastSelected)
857 		fLastSelected--;
858 
859 	if (fAnchorIndex != -1 && index < fAnchorIndex)
860 		fAnchorIndex--;
861 
862 	_RecalcItemTops(index);
863 
864 	_InvalidateFrom(index);
865 	_FixupScrollBar();
866 
867 	return item;
868 }
869 
870 
871 bool
872 BListView::RemoveItem(BListItem* item)
873 {
874 	return BListView::RemoveItem(IndexOf(item)) != NULL;
875 }
876 
877 
878 bool
879 BListView::RemoveItems(int32 index, int32 count)
880 {
881 	if (index >= fList.CountItems())
882 		index = -1;
883 
884 	if (index < 0)
885 		return false;
886 
887 	if (fAnchorIndex != -1 && index < fAnchorIndex)
888 		fAnchorIndex = index;
889 
890 	fList.RemoveItems(index, count);
891 	if (index < fList.CountItems())
892 		_RecalcItemTops(index);
893 
894 	Invalidate();
895 	return true;
896 }
897 
898 
899 void
900 BListView::SetSelectionMessage(BMessage* message)
901 {
902 	delete fSelectMessage;
903 	fSelectMessage = message;
904 }
905 
906 
907 void
908 BListView::SetInvocationMessage(BMessage* message)
909 {
910 	BInvoker::SetMessage(message);
911 }
912 
913 
914 BMessage*
915 BListView::InvocationMessage() const
916 {
917 	return BInvoker::Message();
918 }
919 
920 
921 uint32
922 BListView::InvocationCommand() const
923 {
924 	return BInvoker::Command();
925 }
926 
927 
928 BMessage*
929 BListView::SelectionMessage() const
930 {
931 	return fSelectMessage;
932 }
933 
934 
935 uint32
936 BListView::SelectionCommand() const
937 {
938 	if (fSelectMessage)
939 		return fSelectMessage->what;
940 
941 	return 0;
942 }
943 
944 
945 void
946 BListView::SetListType(list_view_type type)
947 {
948 	if (fListType == B_MULTIPLE_SELECTION_LIST
949 		&& type == B_SINGLE_SELECTION_LIST) {
950 		Select(CurrentSelection(0));
951 	}
952 
953 	fListType = type;
954 }
955 
956 
957 list_view_type
958 BListView::ListType() const
959 {
960 	return fListType;
961 }
962 
963 
964 BListItem*
965 BListView::ItemAt(int32 index) const
966 {
967 	return (BListItem*)fList.ItemAt(index);
968 }
969 
970 
971 int32
972 BListView::IndexOf(BListItem* item) const
973 {
974 	if (Window()) {
975 		if (item != NULL) {
976 			int32 index = IndexOf(BPoint(0.0, item->Top()));
977 			if (index >= 0 && fList.ItemAt(index) == item)
978 				return index;
979 
980 			return -1;
981 		}
982 	}
983 	return fList.IndexOf(item);
984 }
985 
986 
987 int32
988 BListView::IndexOf(BPoint point) const
989 {
990 	int32 low = 0;
991 	int32 high = fList.CountItems() - 1;
992 	int32 mid = -1;
993 	float frameTop = -1.0;
994 	float frameBottom = 1.0;
995 
996 	// binary search the list
997 	while (high >= low) {
998 		mid = (low + high) / 2;
999 		frameTop = ItemAt(mid)->Top();
1000 		frameBottom = ItemAt(mid)->Bottom();
1001 		if (point.y < frameTop)
1002 			high = mid - 1;
1003 		else if (point.y > frameBottom)
1004 			low = mid + 1;
1005 		else
1006 			return mid;
1007 	}
1008 
1009 	return -1;
1010 }
1011 
1012 
1013 BListItem*
1014 BListView::FirstItem() const
1015 {
1016 	return (BListItem*)fList.FirstItem();
1017 }
1018 
1019 
1020 BListItem*
1021 BListView::LastItem() const
1022 {
1023 	return (BListItem*)fList.LastItem();
1024 }
1025 
1026 
1027 bool
1028 BListView::HasItem(BListItem *item) const
1029 {
1030 	return IndexOf(item) != -1;
1031 }
1032 
1033 
1034 int32
1035 BListView::CountItems() const
1036 {
1037 	return fList.CountItems();
1038 }
1039 
1040 
1041 void
1042 BListView::MakeEmpty()
1043 {
1044 	if (fList.IsEmpty())
1045 		return;
1046 
1047 	_DeselectAll(-1, -1);
1048 	fList.MakeEmpty();
1049 
1050 	if (Window()) {
1051 		_FixupScrollBar();
1052 		Invalidate();
1053 	}
1054 }
1055 
1056 
1057 bool
1058 BListView::IsEmpty() const
1059 {
1060 	return fList.IsEmpty();
1061 }
1062 
1063 
1064 void
1065 BListView::DoForEach(bool (*func)(BListItem*))
1066 {
1067 	fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
1068 }
1069 
1070 
1071 void
1072 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg)
1073 {
1074 	fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
1075 }
1076 
1077 
1078 const BListItem**
1079 BListView::Items() const
1080 {
1081 	return (const BListItem**)fList.Items();
1082 }
1083 
1084 
1085 void
1086 BListView::InvalidateItem(int32 index)
1087 {
1088 	Invalidate(ItemFrame(index));
1089 }
1090 
1091 
1092 void
1093 BListView::ScrollToSelection()
1094 {
1095 	BRect itemFrame = ItemFrame(CurrentSelection(0));
1096 
1097 	if (Bounds().Contains(itemFrame))
1098 		return;
1099 
1100 	float scrollPos = itemFrame.top < Bounds().top ?
1101 		itemFrame.top : itemFrame.bottom - Bounds().Height();
1102 
1103 	if (itemFrame.top - scrollPos < Bounds().top)
1104 		scrollPos = itemFrame.top;
1105 
1106 	ScrollTo(itemFrame.left, scrollPos);
1107 }
1108 
1109 
1110 void
1111 BListView::Select(int32 index, bool extend)
1112 {
1113 	if (_Select(index, extend)) {
1114 		SelectionChanged();
1115 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1116 	}
1117 }
1118 
1119 
1120 void
1121 BListView::Select(int32 start, int32 finish, bool extend)
1122 {
1123 	if (_Select(start, finish, extend)) {
1124 		SelectionChanged();
1125 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1126 	}
1127 }
1128 
1129 
1130 bool
1131 BListView::IsItemSelected(int32 index) const
1132 {
1133 	BListItem* item = ItemAt(index);
1134 	if (item != NULL)
1135 		return item->IsSelected();
1136 
1137 	return false;
1138 }
1139 
1140 
1141 int32
1142 BListView::CurrentSelection(int32 index) const
1143 {
1144 	if (fFirstSelected == -1)
1145 		return -1;
1146 
1147 	if (index == 0)
1148 		return fFirstSelected;
1149 
1150 	for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1151 		if (ItemAt(i)->IsSelected()) {
1152 			if (index == 0)
1153 				return i;
1154 
1155 			index--;
1156 		}
1157 	}
1158 
1159 	return -1;
1160 }
1161 
1162 
1163 status_t
1164 BListView::Invoke(BMessage* message)
1165 {
1166 	// Note, this is more or less a copy of BControl::Invoke() and should
1167 	// stay that way (ie. changes done there should be adopted here)
1168 
1169 	bool notify = false;
1170 	uint32 kind = InvokeKind(&notify);
1171 
1172 	BMessage clone(kind);
1173 	status_t err = B_BAD_VALUE;
1174 
1175 	if (!message && !notify)
1176 		message = Message();
1177 
1178 	if (!message) {
1179 		if (!IsWatched())
1180 			return err;
1181 	} else
1182 		clone = *message;
1183 
1184 	clone.AddInt64("when", (int64)system_time());
1185 	clone.AddPointer("source", this);
1186 	clone.AddMessenger("be:sender", BMessenger(this));
1187 
1188 	if (fListType == B_SINGLE_SELECTION_LIST)
1189 		clone.AddInt32("index", fFirstSelected);
1190 	else {
1191 		if (fFirstSelected >= 0) {
1192 			for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1193 				if (ItemAt(i)->IsSelected())
1194 					clone.AddInt32("index", i);
1195 			}
1196 		}
1197 	}
1198 
1199 	if (message)
1200 		err = BInvoker::Invoke(&clone);
1201 
1202 	SendNotices(kind, &clone);
1203 
1204 	return err;
1205 }
1206 
1207 
1208 void
1209 BListView::DeselectAll()
1210 {
1211 	if (_DeselectAll(-1, -1)) {
1212 		SelectionChanged();
1213 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1214 	}
1215 }
1216 
1217 
1218 void
1219 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
1220 {
1221 	if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
1222 		return;
1223 
1224 	if (_DeselectAll(exceptFrom, exceptTo)) {
1225 		SelectionChanged();
1226 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1227 	}
1228 }
1229 
1230 
1231 void
1232 BListView::Deselect(int32 index)
1233 {
1234 	if (_Deselect(index)) {
1235 		SelectionChanged();
1236 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1237 	}
1238 }
1239 
1240 
1241 void
1242 BListView::SelectionChanged()
1243 {
1244 	// Hook method to be implemented by subclasses
1245 }
1246 
1247 
1248 void
1249 BListView::SortItems(int (*cmp)(const void *, const void *))
1250 {
1251 	if (_DeselectAll(-1, -1)) {
1252 		SelectionChanged();
1253 		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1254 	}
1255 
1256 	fList.SortItems(cmp);
1257 	_RecalcItemTops(0);
1258 	Invalidate();
1259 }
1260 
1261 
1262 bool
1263 BListView::SwapItems(int32 a, int32 b)
1264 {
1265 	MiscData data;
1266 
1267 	data.swap.a = a;
1268 	data.swap.b = b;
1269 
1270 	return DoMiscellaneous(B_SWAP_OP, &data);
1271 }
1272 
1273 
1274 bool
1275 BListView::MoveItem(int32 from, int32 to)
1276 {
1277 	MiscData data;
1278 
1279 	data.move.from = from;
1280 	data.move.to = to;
1281 
1282 	return DoMiscellaneous(B_MOVE_OP, &data);
1283 }
1284 
1285 
1286 bool
1287 BListView::ReplaceItem(int32 index, BListItem* item)
1288 {
1289 	MiscData data;
1290 
1291 	data.replace.index = index;
1292 	data.replace.item = item;
1293 
1294 	return DoMiscellaneous(B_REPLACE_OP, &data);
1295 }
1296 
1297 
1298 BRect
1299 BListView::ItemFrame(int32 index)
1300 {
1301 	BRect frame = Bounds();
1302 	if (index < 0 || index >= CountItems()) {
1303 		frame.top = 0;
1304 		frame.bottom = -1;
1305 	} else {
1306 		BListItem* item = ItemAt(index);
1307 		frame.top = item->Top();
1308 		frame.bottom = item->Bottom();
1309 	}
1310 	return frame;
1311 }
1312 
1313 
1314 // #pragma mark -
1315 
1316 
1317 BHandler*
1318 BListView::ResolveSpecifier(BMessage* message, int32 index,
1319 	BMessage* specifier, int32 what, const char* property)
1320 {
1321 	BPropertyInfo propInfo(sProperties);
1322 
1323 	if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) {
1324 		return BView::ResolveSpecifier(message, index, specifier, what,
1325 			property);
1326 	}
1327 
1328 	// TODO: msg->AddInt32("_match_code_", );
1329 
1330 	return this;
1331 }
1332 
1333 
1334 status_t
1335 BListView::GetSupportedSuites(BMessage* data)
1336 {
1337 	if (data == NULL)
1338 		return B_BAD_VALUE;
1339 
1340 	status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
1341 
1342 	BPropertyInfo propertyInfo(sProperties);
1343 	if (err == B_OK)
1344 		err = data->AddFlat("messages", &propertyInfo);
1345 
1346 	if (err == B_OK)
1347 		return BView::GetSupportedSuites(data);
1348 	return err;
1349 }
1350 
1351 
1352 status_t
1353 BListView::Perform(perform_code code, void* _data)
1354 {
1355 	switch (code) {
1356 		case PERFORM_CODE_MIN_SIZE:
1357 			((perform_data_min_size*)_data)->return_value
1358 				= BListView::MinSize();
1359 			return B_OK;
1360 		case PERFORM_CODE_MAX_SIZE:
1361 			((perform_data_max_size*)_data)->return_value
1362 				= BListView::MaxSize();
1363 			return B_OK;
1364 		case PERFORM_CODE_PREFERRED_SIZE:
1365 			((perform_data_preferred_size*)_data)->return_value
1366 				= BListView::PreferredSize();
1367 			return B_OK;
1368 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1369 			((perform_data_layout_alignment*)_data)->return_value
1370 				= BListView::LayoutAlignment();
1371 			return B_OK;
1372 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1373 			((perform_data_has_height_for_width*)_data)->return_value
1374 				= BListView::HasHeightForWidth();
1375 			return B_OK;
1376 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1377 		{
1378 			perform_data_get_height_for_width* data
1379 				= (perform_data_get_height_for_width*)_data;
1380 			BListView::GetHeightForWidth(data->width, &data->min, &data->max,
1381 				&data->preferred);
1382 			return B_OK;
1383 		}
1384 		case PERFORM_CODE_SET_LAYOUT:
1385 		{
1386 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1387 			BListView::SetLayout(data->layout);
1388 			return B_OK;
1389 		}
1390 		case PERFORM_CODE_LAYOUT_INVALIDATED:
1391 		{
1392 			perform_data_layout_invalidated* data
1393 				= (perform_data_layout_invalidated*)_data;
1394 			BListView::LayoutInvalidated(data->descendants);
1395 			return B_OK;
1396 		}
1397 		case PERFORM_CODE_DO_LAYOUT:
1398 		{
1399 			BListView::DoLayout();
1400 			return B_OK;
1401 		}
1402 	}
1403 
1404 	return BView::Perform(code, _data);
1405 }
1406 
1407 
1408 bool
1409 BListView::DoMiscellaneous(MiscCode code, MiscData* data)
1410 {
1411 	if (code > B_SWAP_OP)
1412 		return false;
1413 
1414 	switch (code) {
1415 		case B_NO_OP:
1416 			break;
1417 
1418 		case B_REPLACE_OP:
1419 			return _ReplaceItem(data->replace.index, data->replace.item);
1420 
1421 		case B_MOVE_OP:
1422 			return _MoveItem(data->move.from, data->move.to);
1423 
1424 		case B_SWAP_OP:
1425 			return _SwapItems(data->swap.a, data->swap.b);
1426 	}
1427 
1428 	return false;
1429 }
1430 
1431 
1432 // #pragma mark -
1433 
1434 
1435 void BListView::_ReservedListView2() {}
1436 void BListView::_ReservedListView3() {}
1437 void BListView::_ReservedListView4() {}
1438 
1439 
1440 BListView&
1441 BListView::operator=(const BListView& /*other*/)
1442 {
1443 	return *this;
1444 }
1445 
1446 
1447 // #pragma mark -
1448 
1449 
1450 void
1451 BListView::_InitObject(list_view_type type)
1452 {
1453 	fListType = type;
1454 	fFirstSelected = -1;
1455 	fLastSelected = -1;
1456 	fAnchorIndex = -1;
1457 	fSelectMessage = NULL;
1458 	fScrollView = NULL;
1459 
1460 	fTrack = new track_data;
1461 	fTrack->drag_start = B_ORIGIN;
1462 	fTrack->item_index = -1;
1463 	fTrack->was_selected = false;
1464 	fTrack->try_drag = false;
1465 	fTrack->is_dragging = false;
1466 	fTrack->last_click_time = 0;
1467 
1468 	SetViewUIColor(B_LIST_BACKGROUND_COLOR);
1469 	SetLowUIColor(B_LIST_BACKGROUND_COLOR);
1470 }
1471 
1472 
1473 void
1474 BListView::_FixupScrollBar()
1475 {
1476 
1477 	BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1478 	if (vertScroller != NULL) {
1479 		BRect bounds = Bounds();
1480 		int32 count = CountItems();
1481 
1482 		float itemHeight = 0.0;
1483 
1484 		if (CountItems() > 0)
1485 			itemHeight = ItemAt(CountItems() - 1)->Bottom();
1486 
1487 		if (bounds.Height() > itemHeight) {
1488 			// no scrolling
1489 			vertScroller->SetRange(0.0, 0.0);
1490 			vertScroller->SetValue(0.0);
1491 				// also scrolls ListView to the top
1492 		} else {
1493 			vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1494 			vertScroller->SetProportion(bounds.Height () / itemHeight);
1495 			// scroll up if there is empty room on bottom
1496 			if (itemHeight < bounds.bottom)
1497 				ScrollBy(0.0, bounds.bottom - itemHeight);
1498 		}
1499 
1500 		if (count != 0)
1501 			vertScroller->SetSteps(
1502 				ceilf(FirstItem()->Height()), bounds.Height());
1503 	}
1504 
1505 	BScrollBar* horizontalScroller = ScrollBar(B_HORIZONTAL);
1506 	if (horizontalScroller != NULL) {
1507 		float w;
1508 		GetPreferredSize(&w, NULL);
1509 		BRect scrollBarSize = horizontalScroller->Bounds();
1510 
1511 		if (w <= scrollBarSize.Width()) {
1512 			// no scrolling
1513 			horizontalScroller->SetRange(0.0, 0.0);
1514 			horizontalScroller->SetValue(0.0);
1515 		} else {
1516 			horizontalScroller->SetRange(0, w - scrollBarSize.Width());
1517 			horizontalScroller->SetProportion(scrollBarSize.Width() / w);
1518 		}
1519 	}
1520 }
1521 
1522 
1523 void
1524 BListView::_InvalidateFrom(int32 index)
1525 {
1526 	// make sure index is behind last valid index
1527 	int32 count = CountItems();
1528 	if (index >= count)
1529 		index = count;
1530 
1531 	// take the item before the wanted one,
1532 	// because that might already be removed
1533 	index--;
1534 	BRect dirty = Bounds();
1535 	if (index >= 0)
1536 		dirty.top = ItemFrame(index).bottom + 1;
1537 
1538 	Invalidate(dirty);
1539 }
1540 
1541 
1542 void
1543 BListView::_UpdateItems()
1544 {
1545 	BFont font;
1546 	GetFont(&font);
1547 	for (int32 i = 0; i < CountItems(); i++) {
1548 		ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
1549 		ItemAt(i)->Update(this, &font);
1550 	}
1551 }
1552 
1553 
1554 /*!	Selects the item at the specified \a index, and returns \c true in
1555 	case the selection was changed because of this method.
1556 	If \a extend is \c false, all previously selected items are deselected.
1557 */
1558 bool
1559 BListView::_Select(int32 index, bool extend)
1560 {
1561 	if (index < 0 || index >= CountItems())
1562 		return false;
1563 
1564 	// only lock the window when there is one
1565 	BAutolock locker(Window());
1566 	if (Window() != NULL && !locker.IsLocked())
1567 		return false;
1568 
1569 	bool changed = false;
1570 
1571 	if (!extend && fFirstSelected != -1)
1572 		changed = _DeselectAll(index, index);
1573 
1574 	fAnchorIndex = index;
1575 
1576 	BListItem* item = ItemAt(index);
1577 	if (!item->IsEnabled() || item->IsSelected()) {
1578 		// if the item is already selected, or can't be selected,
1579 		// we're done here
1580 		return changed;
1581 	}
1582 
1583 	// keep track of first and last selected item
1584 	if (fFirstSelected == -1) {
1585 		// no previous selection
1586 		fFirstSelected = index;
1587 		fLastSelected = index;
1588 	} else if (index < fFirstSelected) {
1589 		fFirstSelected = index;
1590 	} else if (index > fLastSelected) {
1591 		fLastSelected = index;
1592 	}
1593 
1594 	item->Select();
1595 	if (Window() != NULL)
1596 		InvalidateItem(index);
1597 
1598 	return true;
1599 }
1600 
1601 
1602 /*!
1603 	Selects the items between \a from and \a to, and returns \c true in
1604 	case the selection was changed because of this method.
1605 	If \a extend is \c false, all previously selected items are deselected.
1606 */
1607 bool
1608 BListView::_Select(int32 from, int32 to, bool extend)
1609 {
1610 	if (to < from)
1611 		return false;
1612 
1613 	BAutolock locker(Window());
1614 	if (Window() && !locker.IsLocked())
1615 		return false;
1616 
1617 	bool changed = false;
1618 
1619 	if (fFirstSelected != -1 && !extend)
1620 		changed = _DeselectAll(from, to);
1621 
1622 	if (fFirstSelected == -1) {
1623 		fFirstSelected = from;
1624 		fLastSelected = to;
1625 	} else {
1626 		if (from < fFirstSelected)
1627 			fFirstSelected = from;
1628 		if (to > fLastSelected)
1629 			fLastSelected = to;
1630 	}
1631 
1632 	for (int32 i = from; i <= to; ++i) {
1633 		BListItem* item = ItemAt(i);
1634 		if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
1635 			item->Select();
1636 			if (Window() != NULL)
1637 				InvalidateItem(i);
1638 			changed = true;
1639 		}
1640 	}
1641 
1642 	return changed;
1643 }
1644 
1645 
1646 bool
1647 BListView::_Deselect(int32 index)
1648 {
1649 	if (index < 0 || index >= CountItems())
1650 		return false;
1651 
1652 	BWindow* window = Window();
1653 	BAutolock locker(window);
1654 	if (window != NULL && !locker.IsLocked())
1655 		return false;
1656 
1657 	BListItem* item = ItemAt(index);
1658 
1659 	if (item != NULL && item->IsSelected()) {
1660 		BRect frame(ItemFrame(index));
1661 		BRect bounds(Bounds());
1662 
1663 		item->Deselect();
1664 
1665 		if (fFirstSelected == index && fLastSelected == index) {
1666 			fFirstSelected = -1;
1667 			fLastSelected = -1;
1668 		} else {
1669 			if (fFirstSelected == index)
1670 				fFirstSelected = _CalcFirstSelected(index);
1671 
1672 			if (fLastSelected == index)
1673 				fLastSelected = _CalcLastSelected(index);
1674 		}
1675 
1676 		if (window && bounds.Intersects(frame))
1677 			DrawItem(ItemAt(index), frame, true);
1678 	}
1679 
1680 	return true;
1681 }
1682 
1683 
1684 bool
1685 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1686 {
1687 	if (fFirstSelected == -1)
1688 		return false;
1689 
1690 	BAutolock locker(Window());
1691 	if (Window() && !locker.IsLocked())
1692 		return false;
1693 
1694 	bool changed = false;
1695 
1696 	for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1697 		// don't deselect the items we shouldn't deselect
1698 		if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1699 			continue;
1700 
1701 		BListItem* item = ItemAt(index);
1702 		if (item != NULL && item->IsSelected()) {
1703 			item->Deselect();
1704 			InvalidateItem(index);
1705 			changed = true;
1706 		}
1707 	}
1708 
1709 	if (!changed)
1710 		return false;
1711 
1712 	if (exceptFrom != -1) {
1713 		fFirstSelected = _CalcFirstSelected(exceptFrom);
1714 		fLastSelected = _CalcLastSelected(exceptTo);
1715 	} else
1716 		fFirstSelected = fLastSelected = -1;
1717 
1718 	return true;
1719 }
1720 
1721 
1722 int32
1723 BListView::_CalcFirstSelected(int32 after)
1724 {
1725 	if (after >= CountItems())
1726 		return -1;
1727 
1728 	int32 count = CountItems();
1729 	for (int32 i = after; i < count; i++) {
1730 		if (ItemAt(i)->IsSelected())
1731 			return i;
1732 	}
1733 
1734 	return -1;
1735 }
1736 
1737 
1738 int32
1739 BListView::_CalcLastSelected(int32 before)
1740 {
1741 	if (before < 0)
1742 		return -1;
1743 
1744 	before = std::min(CountItems() - 1, before);
1745 
1746 	for (int32 i = before; i >= 0; i--) {
1747 		if (ItemAt(i)->IsSelected())
1748 			return i;
1749 	}
1750 
1751 	return -1;
1752 }
1753 
1754 
1755 void
1756 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
1757 {
1758 	if (!item->IsEnabled()) {
1759 		rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
1760 		rgb_color disabledColor;
1761 		if (textColor.red + textColor.green + textColor.blue > 128 * 3)
1762 			disabledColor = tint_color(textColor, B_DARKEN_2_TINT);
1763 		else
1764 			disabledColor = tint_color(textColor, B_LIGHTEN_2_TINT);
1765 
1766 		SetHighColor(disabledColor);
1767 	} else if (item->IsSelected())
1768 		SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
1769 	else
1770 		SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
1771 
1772 	item->DrawItem(this, itemRect, complete);
1773 }
1774 
1775 
1776 bool
1777 BListView::_SwapItems(int32 a, int32 b)
1778 {
1779 	// remember frames of items before anything happens,
1780 	// the tricky situation is when the two items have
1781 	// a different height
1782 	BRect aFrame = ItemFrame(a);
1783 	BRect bFrame = ItemFrame(b);
1784 
1785 	if (!fList.SwapItems(a, b))
1786 		return false;
1787 
1788 	if (a == b) {
1789 		// nothing to do, but success nevertheless
1790 		return true;
1791 	}
1792 
1793 	// track anchor item
1794 	if (fAnchorIndex == a)
1795 		fAnchorIndex = b;
1796 	else if (fAnchorIndex == b)
1797 		fAnchorIndex = a;
1798 
1799 	// track selection
1800 	// NOTE: this is only important if the selection status
1801 	// of both items is not the same
1802 	int32 first = std::min(a, b);
1803 	int32 last = std::max(a, b);
1804 	if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1805 		if (first < fFirstSelected || last > fLastSelected) {
1806 			_RescanSelection(std::min(first, fFirstSelected),
1807 				std::max(last, fLastSelected));
1808 		}
1809 		// though the actually selected items stayed the
1810 		// same, the selection has still changed
1811 		SelectionChanged();
1812 	}
1813 
1814 	ItemAt(a)->SetTop(aFrame.top);
1815 	ItemAt(b)->SetTop(bFrame.top);
1816 
1817 	// take care of invalidation
1818 	if (Window()) {
1819 		// NOTE: window looper is assumed to be locked!
1820 		if (aFrame.Height() != bFrame.Height()) {
1821 			_RecalcItemTops(first, last);
1822 			// items in between shifted visually
1823 			Invalidate(aFrame | bFrame);
1824 		} else {
1825 			Invalidate(aFrame);
1826 			Invalidate(bFrame);
1827 		}
1828 	}
1829 
1830 	return true;
1831 }
1832 
1833 
1834 bool
1835 BListView::_MoveItem(int32 from, int32 to)
1836 {
1837 	// remember item frames before doing anything
1838 	BRect frameFrom = ItemFrame(from);
1839 	BRect frameTo = ItemFrame(to);
1840 
1841 	if (!fList.MoveItem(from, to))
1842 		return false;
1843 
1844 	// track anchor item
1845 	if (fAnchorIndex == from)
1846 		fAnchorIndex = to;
1847 
1848 	// track selection
1849 	if (ItemAt(to)->IsSelected()) {
1850 		_RescanSelection(from, to);
1851 		// though the actually selected items stayed the
1852 		// same, the selection has still changed
1853 		SelectionChanged();
1854 	}
1855 
1856 	_RecalcItemTops((to > from) ? from : to);
1857 
1858 	// take care of invalidation
1859 	if (Window()) {
1860 		// NOTE: window looper is assumed to be locked!
1861 		Invalidate(frameFrom | frameTo);
1862 	}
1863 
1864 	return true;
1865 }
1866 
1867 
1868 bool
1869 BListView::_ReplaceItem(int32 index, BListItem* item)
1870 {
1871 	if (item == NULL)
1872 		return false;
1873 
1874 	BListItem* old = ItemAt(index);
1875 	if (!old)
1876 		return false;
1877 
1878 	BRect frame = ItemFrame(index);
1879 
1880 	bool selectionChanged = old->IsSelected() != item->IsSelected();
1881 
1882 	// replace item
1883 	if (!fList.ReplaceItem(index, item))
1884 		return false;
1885 
1886 	// tack selection
1887 	if (selectionChanged) {
1888 		int32 start = std::min(fFirstSelected, index);
1889 		int32 end = std::max(fLastSelected, index);
1890 		_RescanSelection(start, end);
1891 		SelectionChanged();
1892 	}
1893 	_RecalcItemTops(index);
1894 
1895 	bool itemHeightChanged = frame != ItemFrame(index);
1896 
1897 	// take care of invalidation
1898 	if (Window()) {
1899 		// NOTE: window looper is assumed to be locked!
1900 		if (itemHeightChanged)
1901 			_InvalidateFrom(index);
1902 		else
1903 			Invalidate(frame);
1904 	}
1905 
1906 	if (itemHeightChanged)
1907 		_FixupScrollBar();
1908 
1909 	return true;
1910 }
1911 
1912 
1913 void
1914 BListView::_RescanSelection(int32 from, int32 to)
1915 {
1916 	if (from > to) {
1917 		int32 tmp = from;
1918 		from = to;
1919 		to = tmp;
1920 	}
1921 
1922 	from = std::max((int32)0, from);
1923 	to = std::min(to, CountItems() - 1);
1924 
1925 	if (fAnchorIndex != -1) {
1926 		if (fAnchorIndex == from)
1927 			fAnchorIndex = to;
1928 		else if (fAnchorIndex == to)
1929 			fAnchorIndex = from;
1930 	}
1931 
1932 	for (int32 i = from; i <= to; i++) {
1933 		if (ItemAt(i)->IsSelected()) {
1934 			fFirstSelected = i;
1935 			break;
1936 		}
1937 	}
1938 
1939 	if (fFirstSelected > from)
1940 		from = fFirstSelected;
1941 
1942 	fLastSelected = fFirstSelected;
1943 	for (int32 i = from; i <= to; i++) {
1944 		if (ItemAt(i)->IsSelected())
1945 			fLastSelected = i;
1946 	}
1947 }
1948 
1949 
1950 void
1951 BListView::_RecalcItemTops(int32 start, int32 end)
1952 {
1953 	int32 count = CountItems();
1954 	if ((start < 0) || (start >= count))
1955 		return;
1956 
1957 	if (end >= 0)
1958 		count = end + 1;
1959 
1960 	float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
1961 
1962 	for (int32 i = start; i < count; i++) {
1963 		BListItem *item = ItemAt(i);
1964 		item->SetTop(top);
1965 		top += ceilf(item->Height());
1966 	}
1967 }
1968 
1969 
1970 void
1971 BListView::_DoneTracking(BPoint where)
1972 {
1973 	fTrack->try_drag = false;
1974 	fTrack->is_dragging = false;
1975 }
1976 
1977 
1978 void
1979 BListView::_Track(BPoint where, uint32)
1980 {
1981 	if (fTrack->item_index >= 0 && fTrack->try_drag) {
1982 		// initiate a drag if the mouse was moved far enough
1983 		BPoint offset = where - fTrack->drag_start;
1984 		float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
1985 		if (dragDistance >= 5.0f) {
1986 			fTrack->try_drag = false;
1987 			fTrack->is_dragging = InitiateDrag(fTrack->drag_start,
1988 				fTrack->item_index, fTrack->was_selected);
1989 		}
1990 	}
1991 }
1992