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