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				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
406void
407BListView::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
541void
542BListView::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
634void
635BListView::MouseUp(BPoint where)
636{
637	BView::MouseUp(where);
638}
639
640
641void
642BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
643{
644	BView::MouseMoved(where, code, dragMessage);
645}
646
647
648bool
649BListView::InitiateDrag(BPoint where, int32 index, bool wasSelected)
650{
651	return false;
652}
653
654
655// #pragma mark -
656
657
658void
659BListView::ResizeToPreferred()
660{
661	BView::ResizeToPreferred();
662}
663
664
665void
666BListView::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
687BSize
688BListView::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
696BSize
697BListView::MaxSize()
698{
699	return BView::MaxSize();
700}
701
702
703BSize
704BListView::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
715void
716BListView::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
728void
729BListView::SetFont(const BFont* font, uint32 mask)
730{
731	BView::SetFont(font, mask);
732
733	if (Window() != NULL && !Window()->InViewTransaction())
734		_UpdateItems();
735}
736
737
738void
739BListView::ScrollTo(BPoint point)
740{
741	BView::ScrollTo(point);
742}
743
744
745// #pragma mark - List ops
746
747
748bool
749BListView::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
776bool
777BListView::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
800bool
801BListView::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
833bool
834BListView::AddList(BList* list)
835{
836	return AddList(list, CountItems());
837}
838
839
840BListItem*
841BListView::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
871bool
872BListView::RemoveItem(BListItem* item)
873{
874	return BListView::RemoveItem(IndexOf(item)) != NULL;
875}
876
877
878bool
879BListView::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
899void
900BListView::SetSelectionMessage(BMessage* message)
901{
902	delete fSelectMessage;
903	fSelectMessage = message;
904}
905
906
907void
908BListView::SetInvocationMessage(BMessage* message)
909{
910	BInvoker::SetMessage(message);
911}
912
913
914BMessage*
915BListView::InvocationMessage() const
916{
917	return BInvoker::Message();
918}
919
920
921uint32
922BListView::InvocationCommand() const
923{
924	return BInvoker::Command();
925}
926
927
928BMessage*
929BListView::SelectionMessage() const
930{
931	return fSelectMessage;
932}
933
934
935uint32
936BListView::SelectionCommand() const
937{
938	if (fSelectMessage)
939		return fSelectMessage->what;
940
941	return 0;
942}
943
944
945void
946BListView::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
957list_view_type
958BListView::ListType() const
959{
960	return fListType;
961}
962
963
964BListItem*
965BListView::ItemAt(int32 index) const
966{
967	return (BListItem*)fList.ItemAt(index);
968}
969
970
971int32
972BListView::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
987int32
988BListView::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
1013BListItem*
1014BListView::FirstItem() const
1015{
1016	return (BListItem*)fList.FirstItem();
1017}
1018
1019
1020BListItem*
1021BListView::LastItem() const
1022{
1023	return (BListItem*)fList.LastItem();
1024}
1025
1026
1027bool
1028BListView::HasItem(BListItem *item) const
1029{
1030	return IndexOf(item) != -1;
1031}
1032
1033
1034int32
1035BListView::CountItems() const
1036{
1037	return fList.CountItems();
1038}
1039
1040
1041void
1042BListView::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
1057bool
1058BListView::IsEmpty() const
1059{
1060	return fList.IsEmpty();
1061}
1062
1063
1064void
1065BListView::DoForEach(bool (*func)(BListItem*))
1066{
1067	fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
1068}
1069
1070
1071void
1072BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg)
1073{
1074	fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
1075}
1076
1077
1078const BListItem**
1079BListView::Items() const
1080{
1081	return (const BListItem**)fList.Items();
1082}
1083
1084
1085void
1086BListView::InvalidateItem(int32 index)
1087{
1088	Invalidate(ItemFrame(index));
1089}
1090
1091
1092void
1093BListView::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
1110void
1111BListView::Select(int32 index, bool extend)
1112{
1113	if (_Select(index, extend)) {
1114		SelectionChanged();
1115		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1116	}
1117}
1118
1119
1120void
1121BListView::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
1130bool
1131BListView::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
1141int32
1142BListView::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
1163status_t
1164BListView::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
1208void
1209BListView::DeselectAll()
1210{
1211	if (_DeselectAll(-1, -1)) {
1212		SelectionChanged();
1213		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1214	}
1215}
1216
1217
1218void
1219BListView::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
1231void
1232BListView::Deselect(int32 index)
1233{
1234	if (_Deselect(index)) {
1235		SelectionChanged();
1236		InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1237	}
1238}
1239
1240
1241void
1242BListView::SelectionChanged()
1243{
1244	// Hook method to be implemented by subclasses
1245}
1246
1247
1248void
1249BListView::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
1262bool
1263BListView::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
1274bool
1275BListView::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
1286bool
1287BListView::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
1298BRect
1299BListView::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
1317BHandler*
1318BListView::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
1334status_t
1335BListView::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
1352status_t
1353BListView::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
1408bool
1409BListView::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
1435void BListView::_ReservedListView2() {}
1436void BListView::_ReservedListView3() {}
1437void BListView::_ReservedListView4() {}
1438
1439
1440BListView&
1441BListView::operator=(const BListView& /*other*/)
1442{
1443	return *this;
1444}
1445
1446
1447// #pragma mark -
1448
1449
1450void
1451BListView::_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
1473void
1474BListView::_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
1523void
1524BListView::_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
1542void
1543BListView::_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*/
1558bool
1559BListView::_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*/
1607bool
1608BListView::_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
1646bool
1647BListView::_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
1684bool
1685BListView::_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
1722int32
1723BListView::_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
1738int32
1739BListView::_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
1755void
1756BListView::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
1776bool
1777BListView::_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
1834bool
1835BListView::_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
1868bool
1869BListView::_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
1913void
1914BListView::_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
1950void
1951BListView::_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
1970void
1971BListView::_DoneTracking(BPoint where)
1972{
1973	fTrack->try_drag = false;
1974	fTrack->is_dragging = false;
1975}
1976
1977
1978void
1979BListView::_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