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