vfs: check userland buffers before reading them.
[haiku.git] / src / kits / interface / ListView.cpp
blobfcd870195b56bda3400628bf72cbcb366cb6ec5c
1 /*
2 * Copyright 2001-2015 Haiku, Inc. All rights resrerved.
3 * Distributed under the terms of the MIT license.
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
14 #include <ListView.h>
16 #include <algorithm>
18 #include <stdio.h>
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>
28 #include <binary_compatibility/Interface.h>
31 struct track_data {
32 BPoint drag_start;
33 int32 item_index;
34 bool was_selected;
35 bool try_drag;
36 bool is_dragging;
37 bigtime_t last_click_time;
41 const float kDoubleClickThreshold = 6.0f;
44 static property_info sProperties[] = {
45 { "Item", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
46 "Returns the number of BListItems currently in the list.", 0,
47 { B_INT32_TYPE }
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."
57 { "Selection", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
58 "Returns int32 count of items in the selection.", 0, { B_INT32_TYPE }
61 { "Selection", { B_EXECUTE_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
62 "Invoke items in selection."
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 }
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 }
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 }
82 { 0 }
86 BListView::BListView(BRect frame, const char* name, list_view_type type,
87 uint32 resizingMode, uint32 flags)
89 BView(frame, name, resizingMode, flags)
91 _InitObject(type);
95 BListView::BListView(const char* name, list_view_type type, uint32 flags)
97 BView(name, flags)
99 _InitObject(type);
103 BListView::BListView(list_view_type type)
105 BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE)
107 _InitObject(type);
111 BListView::BListView(BMessage* archive)
113 BView(archive)
115 int32 listType;
116 archive->FindInt32("_lv_type", &listType);
117 _InitObject((list_view_type)listType);
119 int32 i = 0;
120 BMessage subData;
121 while (archive->FindMessage("_l_items", i++, &subData) == B_OK) {
122 BArchivable* object = instantiate_object(&subData);
123 if (object == NULL)
124 continue;
126 BListItem* item = dynamic_cast<BListItem*>(object);
127 if (item != NULL)
128 AddItem(item);
131 if (archive->HasMessage("_msg")) {
132 BMessage* invokationMessage = new BMessage;
134 archive->FindMessage("_msg", invokationMessage);
135 SetInvocationMessage(invokationMessage);
138 if (archive->HasMessage("_2nd_msg")) {
139 BMessage* selectionMessage = new BMessage;
141 archive->FindMessage("_2nd_msg", selectionMessage);
142 SetSelectionMessage(selectionMessage);
147 BListView::~BListView()
149 // NOTE: According to BeBook, BListView does not free the items itself.
150 delete fTrack;
151 SetSelectionMessage(NULL);
155 // #pragma mark -
158 BArchivable*
159 BListView::Instantiate(BMessage* archive)
161 if (validate_instantiation(archive, "BListView"))
162 return new BListView(archive);
164 return NULL;
168 status_t
169 BListView::Archive(BMessage* data, bool deep) const
171 status_t status = BView::Archive(data, deep);
172 if (status < B_OK)
173 return status;
175 status = data->AddInt32("_lv_type", fListType);
176 if (status == B_OK && deep) {
177 BListItem* item;
178 int32 i = 0;
180 while ((item = ItemAt(i++)) != NULL) {
181 BMessage subData;
182 status = item->Archive(&subData, true);
183 if (status >= B_OK)
184 status = data->AddMessage("_l_items", &subData);
186 if (status < B_OK)
187 break;
191 if (status >= B_OK && InvocationMessage() != NULL)
192 status = data->AddMessage("_msg", InvocationMessage());
194 if (status == B_OK && fSelectMessage != NULL)
195 status = data->AddMessage("_2nd_msg", fSelectMessage);
197 return status;
201 // #pragma mark -
204 void
205 BListView::Draw(BRect updateRect)
207 int32 count = CountItems();
208 if (count == 0)
209 return;
211 BRect itemFrame(0, 0, Bounds().right, -1);
212 for (int i = 0; i < count; i++) {
213 BListItem* item = ItemAt(i);
214 itemFrame.bottom = itemFrame.top + ceilf(item->Height()) - 1;
216 if (itemFrame.Intersects(updateRect))
217 DrawItem(item, itemFrame);
219 itemFrame.top = itemFrame.bottom + 1;
224 void
225 BListView::AttachedToWindow()
227 BView::AttachedToWindow();
228 _UpdateItems();
230 if (!Messenger().IsValid())
231 SetTarget(Window(), NULL);
233 _FixupScrollBar();
237 void
238 BListView::DetachedFromWindow()
240 BView::DetachedFromWindow();
244 void
245 BListView::AllAttached()
247 BView::AllAttached();
251 void
252 BListView::AllDetached()
254 BView::AllDetached();
258 void
259 BListView::FrameResized(float newWidth, float newHeight)
261 _FixupScrollBar();
263 // notify items of new width.
264 _UpdateItems();
268 void
269 BListView::FrameMoved(BPoint newPosition)
271 BView::FrameMoved(newPosition);
275 void
276 BListView::TargetedByScrollView(BScrollView* view)
278 fScrollView = view;
279 // TODO: We could SetFlags(Flags() | B_FRAME_EVENTS) here, but that
280 // may mess up application code which manages this by some other means
281 // and doesn't want us to be messing with flags.
285 void
286 BListView::WindowActivated(bool active)
288 BView::WindowActivated(active);
292 // #pragma mark -
295 void
296 BListView::MessageReceived(BMessage* message)
298 switch (message->what) {
299 case B_MOUSE_WHEEL_CHANGED:
300 if (!fTrack->is_dragging)
301 BView::MessageReceived(message);
302 break;
304 case B_COUNT_PROPERTIES:
305 case B_EXECUTE_PROPERTY:
306 case B_GET_PROPERTY:
307 case B_SET_PROPERTY:
309 BPropertyInfo propInfo(sProperties);
310 BMessage specifier;
311 const char* property;
313 if (message->GetCurrentSpecifier(NULL, &specifier) != B_OK
314 || specifier.FindString("property", &property) != B_OK) {
315 return;
318 switch (propInfo.FindMatch(message, 0, &specifier, message->what,
319 property)) {
320 case B_ERROR:
321 BView::MessageReceived(message);
322 break;
324 case 0:
326 BMessage reply(B_REPLY);
327 reply.AddInt32("result", CountItems());
328 reply.AddInt32("error", B_OK);
330 message->SendReply(&reply);
331 break;
334 case 1:
335 break;
337 case 2:
339 int32 count = 0;
341 for (int32 i = 0; i < CountItems(); i++) {
342 if (ItemAt(i)->IsSelected())
343 count++;
346 BMessage reply(B_REPLY);
347 reply.AddInt32("result", count);
348 reply.AddInt32("error", B_OK);
350 message->SendReply(&reply);
351 break;
354 case 3:
355 break;
357 case 4:
359 BMessage reply (B_REPLY);
361 for (int32 i = 0; i < CountItems(); i++) {
362 if (ItemAt(i)->IsSelected())
363 reply.AddInt32("result", i);
366 reply.AddInt32("error", B_OK);
368 message->SendReply(&reply);
369 break;
372 case 5:
373 break;
375 case 6:
377 BMessage reply(B_REPLY);
379 bool select;
380 if (message->FindBool("data", &select) == B_OK && select)
381 Select(0, CountItems() - 1, false);
382 else
383 DeselectAll();
385 reply.AddInt32("error", B_OK);
387 message->SendReply(&reply);
388 break;
391 break;
394 case B_SELECT_ALL:
395 if (fListType == B_MULTIPLE_SELECTION_LIST)
396 Select(0, CountItems() - 1, false);
397 break;
399 default:
400 BView::MessageReceived(message);
405 void
406 BListView::KeyDown(const char* bytes, int32 numBytes)
408 bool extend = fListType == B_MULTIPLE_SELECTION_LIST
409 && (modifiers() & B_SHIFT_KEY) != 0;
411 if (fFirstSelected == -1
412 && (bytes[0] == B_UP_ARROW || bytes[0] == B_DOWN_ARROW)) {
413 // nothing is selected yet, select the first enabled item
414 int32 lastItem = CountItems() - 1;
415 for (int32 i = 0; i <= lastItem; i++) {
416 if (ItemAt(i)->IsEnabled()) {
417 Select(i);
418 break;
421 return;
424 switch (bytes[0]) {
425 case B_UP_ARROW:
427 if (fAnchorIndex > 0) {
428 if (!extend || fAnchorIndex <= fFirstSelected) {
429 for (int32 i = 1; fAnchorIndex - i >= 0; i++) {
430 if (ItemAt(fAnchorIndex - i)->IsEnabled()) {
431 // Select the previous enabled item
432 Select(fAnchorIndex - i, extend);
433 break;
436 } else {
437 Deselect(fAnchorIndex);
439 fAnchorIndex--;
440 while (fAnchorIndex > 0
441 && !ItemAt(fAnchorIndex)->IsEnabled());
445 ScrollToSelection();
446 break;
449 case B_DOWN_ARROW:
451 int32 lastItem = CountItems() - 1;
452 if (fAnchorIndex < lastItem) {
453 if (!extend || fAnchorIndex >= fLastSelected) {
454 for (int32 i = 1; fAnchorIndex + i <= lastItem; i++) {
455 if (ItemAt(fAnchorIndex + i)->IsEnabled()) {
456 // Select the next enabled item
457 Select(fAnchorIndex + i, extend);
458 break;
461 } else {
462 Deselect(fAnchorIndex);
464 fAnchorIndex++;
465 while (fAnchorIndex < lastItem
466 && !ItemAt(fAnchorIndex)->IsEnabled());
470 ScrollToSelection();
471 break;
474 case B_HOME:
475 if (extend) {
476 Select(0, fAnchorIndex, true);
477 fAnchorIndex = 0;
478 } else {
479 // select the first enabled item
480 int32 lastItem = CountItems() - 1;
481 for (int32 i = 0; i <= lastItem; i++) {
482 if (ItemAt(i)->IsEnabled()) {
483 Select(i, false);
484 break;
489 ScrollToSelection();
490 break;
492 case B_END:
493 if (extend) {
494 Select(fAnchorIndex, CountItems() - 1, true);
495 fAnchorIndex = CountItems() - 1;
496 } else {
497 // select the last enabled item
498 for (int32 i = CountItems() - 1; i >= 0; i--) {
499 if (ItemAt(i)->IsEnabled()) {
500 Select(i, false);
501 break;
506 ScrollToSelection();
507 break;
509 case B_PAGE_UP:
511 BPoint scrollOffset(LeftTop());
512 scrollOffset.y = std::max(0.0f, scrollOffset.y - Bounds().Height());
513 ScrollTo(scrollOffset);
514 break;
517 case B_PAGE_DOWN:
519 BPoint scrollOffset(LeftTop());
520 if (BListItem* item = LastItem()) {
521 scrollOffset.y += Bounds().Height();
522 scrollOffset.y = std::min(item->Bottom() - Bounds().Height(),
523 scrollOffset.y);
525 ScrollTo(scrollOffset);
526 break;
529 case B_RETURN:
530 case B_SPACE:
531 Invoke();
532 break;
534 default:
535 BView::KeyDown(bytes, numBytes);
540 void
541 BListView::MouseDown(BPoint where)
543 if (!IsFocus()) {
544 MakeFocus();
545 Sync();
546 Window()->UpdateIfNeeded();
549 BMessage* message = Looper()->CurrentMessage();
550 int32 index = IndexOf(where);
552 int32 buttons = 0;
553 if (message != NULL)
554 message->FindInt32("buttons", &buttons);
556 int32 modifiers = 0;
557 if (message != NULL)
558 message->FindInt32("modifiers", &modifiers);
560 // If the user double (or more) clicked within the current selection,
561 // we don't change the selection but invoke the selection.
562 // TODO: move this code someplace where it can be shared everywhere
563 // instead of every class having to reimplement it, once some sane
564 // API for it is decided.
565 BPoint delta = where - fTrack->drag_start;
566 bigtime_t sysTime;
567 Window()->CurrentMessage()->FindInt64("when", &sysTime);
568 bigtime_t timeDelta = sysTime - fTrack->last_click_time;
569 bigtime_t doubleClickSpeed;
570 get_click_speed(&doubleClickSpeed);
571 bool doubleClick = false;
573 if (timeDelta < doubleClickSpeed
574 && fabs(delta.x) < kDoubleClickThreshold
575 && fabs(delta.y) < kDoubleClickThreshold
576 && fTrack->item_index == index) {
577 doubleClick = true;
580 if (doubleClick && index >= fFirstSelected && index <= fLastSelected) {
581 fTrack->drag_start.Set(INT32_MAX, INT32_MAX);
582 Invoke();
583 return BView::MouseDown(where);
586 if (!doubleClick) {
587 fTrack->drag_start = where;
588 fTrack->last_click_time = system_time();
589 fTrack->item_index = index;
590 fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false;
591 fTrack->try_drag = true;
593 MouseDownThread<BListView>::TrackMouse(this,
594 &BListView::_DoneTracking, &BListView::_Track);
597 if (index >= 0) {
598 if (fListType == B_MULTIPLE_SELECTION_LIST) {
599 if ((modifiers & B_SHIFT_KEY) != 0) {
600 // select entire block
601 // TODO: maybe review if we want it like in Tracker
602 // (anchor item)
603 if (index >= fFirstSelected && index < fLastSelected) {
604 // clicked inside of selected items block, deselect all
605 // but from the first selected item to the clicked item
606 DeselectExcept(fFirstSelected, index);
607 } else {
608 Select(std::min(index, fFirstSelected), std::max(index,
609 fLastSelected));
611 } else {
612 if ((modifiers & B_COMMAND_KEY) != 0) {
613 // toggle selection state of clicked item (like in Tracker)
614 // toggle selection state of clicked item
615 if (ItemAt(index)->IsSelected())
616 Deselect(index);
617 else
618 Select(index, true);
619 } else
620 Select(index);
622 } else {
623 // toggle selection state of clicked item
624 if ((modifiers & B_COMMAND_KEY) != 0 && ItemAt(index)->IsSelected())
625 Deselect(index);
626 else
627 Select(index);
629 } else if ((modifiers & B_COMMAND_KEY) == 0)
630 DeselectAll();
632 BView::MouseDown(where);
636 void
637 BListView::MouseUp(BPoint where)
639 BView::MouseUp(where);
643 void
644 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
646 BView::MouseMoved(where, code, dragMessage);
650 bool
651 BListView::InitiateDrag(BPoint where, int32 index, bool wasSelected)
653 return false;
657 // #pragma mark -
660 void
661 BListView::ResizeToPreferred()
663 BView::ResizeToPreferred();
667 void
668 BListView::GetPreferredSize(float *_width, float *_height)
670 int32 count = CountItems();
672 if (count > 0) {
673 float maxWidth = 0.0;
674 for (int32 i = 0; i < count; i++) {
675 float itemWidth = ItemAt(i)->Width();
676 if (itemWidth > maxWidth)
677 maxWidth = itemWidth;
680 if (_width != NULL)
681 *_width = maxWidth;
682 if (_height != NULL)
683 *_height = ItemAt(count - 1)->Bottom();
684 } else
685 BView::GetPreferredSize(_width, _height);
689 BSize
690 BListView::MinSize()
692 // We need a stable min size: the BView implementation uses
693 // GetPreferredSize(), which by default just returns the current size.
694 return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
698 BSize
699 BListView::MaxSize()
701 return BView::MaxSize();
705 BSize
706 BListView::PreferredSize()
708 // We need a stable preferred size: the BView implementation uses
709 // GetPreferredSize(), which by default just returns the current size.
710 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
714 // #pragma mark -
717 void
718 BListView::MakeFocus(bool focused)
720 if (IsFocus() == focused)
721 return;
723 BView::MakeFocus(focused);
725 if (fScrollView)
726 fScrollView->SetBorderHighlighted(focused);
730 void
731 BListView::SetFont(const BFont* font, uint32 mask)
733 BView::SetFont(font, mask);
735 if (Window() != NULL && !Window()->InViewTransaction())
736 _UpdateItems();
740 void
741 BListView::ScrollTo(BPoint point)
743 BView::ScrollTo(point);
747 // #pragma mark - List ops
750 bool
751 BListView::AddItem(BListItem* item, int32 index)
753 if (!fList.AddItem(item, index))
754 return false;
756 if (fFirstSelected != -1 && index <= fFirstSelected)
757 fFirstSelected++;
759 if (fLastSelected != -1 && index <= fLastSelected)
760 fLastSelected++;
762 if (Window()) {
763 BFont font;
764 GetFont(&font);
765 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
767 item->Update(this, &font);
768 _RecalcItemTops(index + 1);
770 _FixupScrollBar();
771 _InvalidateFrom(index);
774 return true;
778 bool
779 BListView::AddItem(BListItem* item)
781 if (!fList.AddItem(item))
782 return false;
784 // No need to adapt selection, as this item is the last in the list
786 if (Window()) {
787 BFont font;
788 GetFont(&font);
789 int32 index = CountItems() - 1;
790 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
792 item->Update(this, &font);
794 _FixupScrollBar();
795 InvalidateItem(CountItems() - 1);
798 return true;
802 bool
803 BListView::AddList(BList* list, int32 index)
805 if (!fList.AddList(list, index))
806 return false;
808 int32 count = list->CountItems();
810 if (fFirstSelected != -1 && index < fFirstSelected)
811 fFirstSelected += count;
813 if (fLastSelected != -1 && index < fLastSelected)
814 fLastSelected += count;
816 if (Window()) {
817 BFont font;
818 GetFont(&font);
820 for (int32 i = index; i <= (index + count - 1); i++) {
821 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
822 ItemAt(i)->Update(this, &font);
825 _RecalcItemTops(index + count - 1);
827 _FixupScrollBar();
828 Invalidate(); // TODO
831 return true;
835 bool
836 BListView::AddList(BList* list)
838 return AddList(list, CountItems());
842 BListItem*
843 BListView::RemoveItem(int32 index)
845 BListItem* item = ItemAt(index);
846 if (item == NULL)
847 return NULL;
849 if (item->IsSelected())
850 Deselect(index);
852 if (!fList.RemoveItem(item))
853 return NULL;
855 if (fFirstSelected != -1 && index < fFirstSelected)
856 fFirstSelected--;
858 if (fLastSelected != -1 && index < fLastSelected)
859 fLastSelected--;
861 if (fAnchorIndex != -1 && index < fAnchorIndex)
862 fAnchorIndex--;
864 _RecalcItemTops(index);
866 _InvalidateFrom(index);
867 _FixupScrollBar();
869 return item;
873 bool
874 BListView::RemoveItem(BListItem* item)
876 return BListView::RemoveItem(IndexOf(item)) != NULL;
880 bool
881 BListView::RemoveItems(int32 index, int32 count)
883 if (index >= fList.CountItems())
884 index = -1;
886 if (index < 0)
887 return false;
889 if (fAnchorIndex != -1 && index < fAnchorIndex)
890 fAnchorIndex = index;
892 fList.RemoveItems(index, count);
893 if (index < fList.CountItems())
894 _RecalcItemTops(index);
896 Invalidate();
897 return true;
901 void
902 BListView::SetSelectionMessage(BMessage* message)
904 delete fSelectMessage;
905 fSelectMessage = message;
909 void
910 BListView::SetInvocationMessage(BMessage* message)
912 BInvoker::SetMessage(message);
916 BMessage*
917 BListView::InvocationMessage() const
919 return BInvoker::Message();
923 uint32
924 BListView::InvocationCommand() const
926 return BInvoker::Command();
930 BMessage*
931 BListView::SelectionMessage() const
933 return fSelectMessage;
937 uint32
938 BListView::SelectionCommand() const
940 if (fSelectMessage)
941 return fSelectMessage->what;
943 return 0;
947 void
948 BListView::SetListType(list_view_type type)
950 if (fListType == B_MULTIPLE_SELECTION_LIST
951 && type == B_SINGLE_SELECTION_LIST) {
952 Select(CurrentSelection(0));
955 fListType = type;
959 list_view_type
960 BListView::ListType() const
962 return fListType;
966 BListItem*
967 BListView::ItemAt(int32 index) const
969 return (BListItem*)fList.ItemAt(index);
973 int32
974 BListView::IndexOf(BListItem* item) const
976 if (Window()) {
977 if (item != NULL) {
978 int32 index = IndexOf(BPoint(0.0, item->Top()));
979 if (index >= 0 && fList.ItemAt(index) == item)
980 return index;
982 return -1;
985 return fList.IndexOf(item);
989 int32
990 BListView::IndexOf(BPoint point) const
992 int32 low = 0;
993 int32 high = fList.CountItems() - 1;
994 int32 mid = -1;
995 float frameTop = -1.0;
996 float frameBottom = 1.0;
998 // binary search the list
999 while (high >= low) {
1000 mid = (low + high) / 2;
1001 frameTop = ItemAt(mid)->Top();
1002 frameBottom = ItemAt(mid)->Bottom();
1003 if (point.y < frameTop)
1004 high = mid - 1;
1005 else if (point.y > frameBottom)
1006 low = mid + 1;
1007 else
1008 return mid;
1011 return -1;
1015 BListItem*
1016 BListView::FirstItem() const
1018 return (BListItem*)fList.FirstItem();
1022 BListItem*
1023 BListView::LastItem() const
1025 return (BListItem*)fList.LastItem();
1029 bool
1030 BListView::HasItem(BListItem *item) const
1032 return IndexOf(item) != -1;
1036 int32
1037 BListView::CountItems() const
1039 return fList.CountItems();
1043 void
1044 BListView::MakeEmpty()
1046 if (fList.IsEmpty())
1047 return;
1049 _DeselectAll(-1, -1);
1050 fList.MakeEmpty();
1052 if (Window()) {
1053 _FixupScrollBar();
1054 Invalidate();
1059 bool
1060 BListView::IsEmpty() const
1062 return fList.IsEmpty();
1066 void
1067 BListView::DoForEach(bool (*func)(BListItem*))
1069 fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
1073 void
1074 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg)
1076 fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
1080 const BListItem**
1081 BListView::Items() const
1083 return (const BListItem**)fList.Items();
1087 void
1088 BListView::InvalidateItem(int32 index)
1090 Invalidate(ItemFrame(index));
1094 void
1095 BListView::ScrollToSelection()
1097 BRect itemFrame = ItemFrame(CurrentSelection(0));
1099 if (Bounds().Contains(itemFrame))
1100 return;
1102 float scrollPos = itemFrame.top < Bounds().top ?
1103 itemFrame.top : itemFrame.bottom - Bounds().Height();
1105 if (itemFrame.top - scrollPos < Bounds().top)
1106 scrollPos = itemFrame.top;
1108 ScrollTo(itemFrame.left, scrollPos);
1112 void
1113 BListView::Select(int32 index, bool extend)
1115 if (_Select(index, extend)) {
1116 SelectionChanged();
1117 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1122 void
1123 BListView::Select(int32 start, int32 finish, bool extend)
1125 if (_Select(start, finish, extend)) {
1126 SelectionChanged();
1127 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1132 bool
1133 BListView::IsItemSelected(int32 index) const
1135 BListItem* item = ItemAt(index);
1136 if (item != NULL)
1137 return item->IsSelected();
1139 return false;
1143 int32
1144 BListView::CurrentSelection(int32 index) const
1146 if (fFirstSelected == -1)
1147 return -1;
1149 if (index == 0)
1150 return fFirstSelected;
1152 for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1153 if (ItemAt(i)->IsSelected()) {
1154 if (index == 0)
1155 return i;
1157 index--;
1161 return -1;
1165 status_t
1166 BListView::Invoke(BMessage* message)
1168 // Note, this is more or less a copy of BControl::Invoke() and should
1169 // stay that way (ie. changes done there should be adopted here)
1171 bool notify = false;
1172 uint32 kind = InvokeKind(&notify);
1174 BMessage clone(kind);
1175 status_t err = B_BAD_VALUE;
1177 if (!message && !notify)
1178 message = Message();
1180 if (!message) {
1181 if (!IsWatched())
1182 return err;
1183 } else
1184 clone = *message;
1186 clone.AddInt64("when", (int64)system_time());
1187 clone.AddPointer("source", this);
1188 clone.AddMessenger("be:sender", BMessenger(this));
1190 if (fListType == B_SINGLE_SELECTION_LIST)
1191 clone.AddInt32("index", fFirstSelected);
1192 else {
1193 if (fFirstSelected >= 0) {
1194 for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1195 if (ItemAt(i)->IsSelected())
1196 clone.AddInt32("index", i);
1201 if (message)
1202 err = BInvoker::Invoke(&clone);
1204 SendNotices(kind, &clone);
1206 return err;
1210 void
1211 BListView::DeselectAll()
1213 if (_DeselectAll(-1, -1)) {
1214 SelectionChanged();
1215 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1220 void
1221 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
1223 if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
1224 return;
1226 if (_DeselectAll(exceptFrom, exceptTo)) {
1227 SelectionChanged();
1228 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1233 void
1234 BListView::Deselect(int32 index)
1236 if (_Deselect(index)) {
1237 SelectionChanged();
1238 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1243 void
1244 BListView::SelectionChanged()
1246 // Hook method to be implemented by subclasses
1250 void
1251 BListView::SortItems(int (*cmp)(const void *, const void *))
1253 if (_DeselectAll(-1, -1)) {
1254 SelectionChanged();
1255 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1258 fList.SortItems(cmp);
1259 _RecalcItemTops(0);
1260 Invalidate();
1264 bool
1265 BListView::SwapItems(int32 a, int32 b)
1267 MiscData data;
1269 data.swap.a = a;
1270 data.swap.b = b;
1272 return DoMiscellaneous(B_SWAP_OP, &data);
1276 bool
1277 BListView::MoveItem(int32 from, int32 to)
1279 MiscData data;
1281 data.move.from = from;
1282 data.move.to = to;
1284 return DoMiscellaneous(B_MOVE_OP, &data);
1288 bool
1289 BListView::ReplaceItem(int32 index, BListItem* item)
1291 MiscData data;
1293 data.replace.index = index;
1294 data.replace.item = item;
1296 return DoMiscellaneous(B_REPLACE_OP, &data);
1300 BRect
1301 BListView::ItemFrame(int32 index)
1303 BRect frame = Bounds();
1304 if (index < 0 || index >= CountItems()) {
1305 frame.top = 0;
1306 frame.bottom = -1;
1307 } else {
1308 BListItem* item = ItemAt(index);
1309 frame.top = item->Top();
1310 frame.bottom = item->Bottom();
1312 return frame;
1316 // #pragma mark -
1319 BHandler*
1320 BListView::ResolveSpecifier(BMessage* message, int32 index,
1321 BMessage* specifier, int32 what, const char* property)
1323 BPropertyInfo propInfo(sProperties);
1325 if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) {
1326 return BView::ResolveSpecifier(message, index, specifier, what,
1327 property);
1330 // TODO: msg->AddInt32("_match_code_", );
1332 return this;
1336 status_t
1337 BListView::GetSupportedSuites(BMessage* data)
1339 if (data == NULL)
1340 return B_BAD_VALUE;
1342 status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
1344 BPropertyInfo propertyInfo(sProperties);
1345 if (err == B_OK)
1346 err = data->AddFlat("messages", &propertyInfo);
1348 if (err == B_OK)
1349 return BView::GetSupportedSuites(data);
1350 return err;
1354 status_t
1355 BListView::Perform(perform_code code, void* _data)
1357 switch (code) {
1358 case PERFORM_CODE_MIN_SIZE:
1359 ((perform_data_min_size*)_data)->return_value
1360 = BListView::MinSize();
1361 return B_OK;
1362 case PERFORM_CODE_MAX_SIZE:
1363 ((perform_data_max_size*)_data)->return_value
1364 = BListView::MaxSize();
1365 return B_OK;
1366 case PERFORM_CODE_PREFERRED_SIZE:
1367 ((perform_data_preferred_size*)_data)->return_value
1368 = BListView::PreferredSize();
1369 return B_OK;
1370 case PERFORM_CODE_LAYOUT_ALIGNMENT:
1371 ((perform_data_layout_alignment*)_data)->return_value
1372 = BListView::LayoutAlignment();
1373 return B_OK;
1374 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1375 ((perform_data_has_height_for_width*)_data)->return_value
1376 = BListView::HasHeightForWidth();
1377 return B_OK;
1378 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1380 perform_data_get_height_for_width* data
1381 = (perform_data_get_height_for_width*)_data;
1382 BListView::GetHeightForWidth(data->width, &data->min, &data->max,
1383 &data->preferred);
1384 return B_OK;
1386 case PERFORM_CODE_SET_LAYOUT:
1388 perform_data_set_layout* data = (perform_data_set_layout*)_data;
1389 BListView::SetLayout(data->layout);
1390 return B_OK;
1392 case PERFORM_CODE_LAYOUT_INVALIDATED:
1394 perform_data_layout_invalidated* data
1395 = (perform_data_layout_invalidated*)_data;
1396 BListView::LayoutInvalidated(data->descendants);
1397 return B_OK;
1399 case PERFORM_CODE_DO_LAYOUT:
1401 BListView::DoLayout();
1402 return B_OK;
1406 return BView::Perform(code, _data);
1410 bool
1411 BListView::DoMiscellaneous(MiscCode code, MiscData* data)
1413 if (code > B_SWAP_OP)
1414 return false;
1416 switch (code) {
1417 case B_NO_OP:
1418 break;
1420 case B_REPLACE_OP:
1421 return _ReplaceItem(data->replace.index, data->replace.item);
1423 case B_MOVE_OP:
1424 return _MoveItem(data->move.from, data->move.to);
1426 case B_SWAP_OP:
1427 return _SwapItems(data->swap.a, data->swap.b);
1430 return false;
1434 // #pragma mark -
1437 void BListView::_ReservedListView2() {}
1438 void BListView::_ReservedListView3() {}
1439 void BListView::_ReservedListView4() {}
1442 BListView&
1443 BListView::operator=(const BListView& /*other*/)
1445 return *this;
1449 // #pragma mark -
1452 void
1453 BListView::_InitObject(list_view_type type)
1455 fListType = type;
1456 fFirstSelected = -1;
1457 fLastSelected = -1;
1458 fAnchorIndex = -1;
1459 fSelectMessage = NULL;
1460 fScrollView = NULL;
1462 fTrack = new track_data;
1463 fTrack->drag_start = B_ORIGIN;
1464 fTrack->item_index = -1;
1465 fTrack->was_selected = false;
1466 fTrack->try_drag = false;
1467 fTrack->is_dragging = false;
1468 fTrack->last_click_time = 0;
1470 SetViewUIColor(B_LIST_BACKGROUND_COLOR);
1471 SetLowUIColor(B_LIST_BACKGROUND_COLOR);
1475 void
1476 BListView::_FixupScrollBar()
1479 BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1480 if (vertScroller != NULL) {
1481 BRect bounds = Bounds();
1482 int32 count = CountItems();
1484 float itemHeight = 0.0;
1486 if (CountItems() > 0)
1487 itemHeight = ItemAt(CountItems() - 1)->Bottom();
1489 if (bounds.Height() > itemHeight) {
1490 // no scrolling
1491 vertScroller->SetRange(0.0, 0.0);
1492 vertScroller->SetValue(0.0);
1493 // also scrolls ListView to the top
1494 } else {
1495 vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1496 vertScroller->SetProportion(bounds.Height () / itemHeight);
1497 // scroll up if there is empty room on bottom
1498 if (itemHeight < bounds.bottom)
1499 ScrollBy(0.0, bounds.bottom - itemHeight);
1502 if (count != 0)
1503 vertScroller->SetSteps(
1504 ceilf(FirstItem()->Height()), bounds.Height());
1507 BScrollBar* horizontalScroller = ScrollBar(B_HORIZONTAL);
1508 if (horizontalScroller != NULL) {
1509 float w;
1510 GetPreferredSize(&w, NULL);
1511 BRect scrollBarSize = horizontalScroller->Bounds();
1513 if (w <= scrollBarSize.Width()) {
1514 // no scrolling
1515 horizontalScroller->SetRange(0.0, 0.0);
1516 horizontalScroller->SetValue(0.0);
1517 } else {
1518 horizontalScroller->SetRange(0, w - scrollBarSize.Width());
1519 horizontalScroller->SetProportion(scrollBarSize.Width() / w);
1521 printf("Range: %f - %f\n", w, scrollBarSize.Width());
1526 void
1527 BListView::_InvalidateFrom(int32 index)
1529 // make sure index is behind last valid index
1530 int32 count = CountItems();
1531 if (index >= count)
1532 index = count;
1534 // take the item before the wanted one,
1535 // because that might already be removed
1536 index--;
1537 BRect dirty = Bounds();
1538 if (index >= 0)
1539 dirty.top = ItemFrame(index).bottom + 1;
1541 Invalidate(dirty);
1545 void
1546 BListView::_UpdateItems()
1548 BFont font;
1549 GetFont(&font);
1550 for (int32 i = 0; i < CountItems(); i++) {
1551 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
1552 ItemAt(i)->Update(this, &font);
1557 /*! Selects the item at the specified \a index, and returns \c true in
1558 case the selection was changed because of this method.
1559 If \a extend is \c false, all previously selected items are deselected.
1561 bool
1562 BListView::_Select(int32 index, bool extend)
1564 if (index < 0 || index >= CountItems())
1565 return false;
1567 // only lock the window when there is one
1568 BAutolock locker(Window());
1569 if (Window() != NULL && !locker.IsLocked())
1570 return false;
1572 bool changed = false;
1574 if (!extend && fFirstSelected != -1)
1575 changed = _DeselectAll(index, index);
1577 fAnchorIndex = index;
1579 BListItem* item = ItemAt(index);
1580 if (!item->IsEnabled() || item->IsSelected()) {
1581 // if the item is already selected, or can't be selected,
1582 // we're done here
1583 return changed;
1586 // keep track of first and last selected item
1587 if (fFirstSelected == -1) {
1588 // no previous selection
1589 fFirstSelected = index;
1590 fLastSelected = index;
1591 } else if (index < fFirstSelected) {
1592 fFirstSelected = index;
1593 } else if (index > fLastSelected) {
1594 fLastSelected = index;
1597 item->Select();
1598 if (Window() != NULL)
1599 InvalidateItem(index);
1601 return true;
1606 Selects the items between \a from and \a to, and returns \c true in
1607 case the selection was changed because of this method.
1608 If \a extend is \c false, all previously selected items are deselected.
1610 bool
1611 BListView::_Select(int32 from, int32 to, bool extend)
1613 if (to < from)
1614 return false;
1616 BAutolock locker(Window());
1617 if (Window() && !locker.IsLocked())
1618 return false;
1620 bool changed = false;
1622 if (fFirstSelected != -1 && !extend)
1623 changed = _DeselectAll(from, to);
1625 if (fFirstSelected == -1) {
1626 fFirstSelected = from;
1627 fLastSelected = to;
1628 } else {
1629 if (from < fFirstSelected)
1630 fFirstSelected = from;
1631 if (to > fLastSelected)
1632 fLastSelected = to;
1635 for (int32 i = from; i <= to; ++i) {
1636 BListItem* item = ItemAt(i);
1637 if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
1638 item->Select();
1639 if (Window() != NULL)
1640 InvalidateItem(i);
1641 changed = true;
1645 return changed;
1649 bool
1650 BListView::_Deselect(int32 index)
1652 if (index < 0 || index >= CountItems())
1653 return false;
1655 BWindow* window = Window();
1656 BAutolock locker(window);
1657 if (window != NULL && !locker.IsLocked())
1658 return false;
1660 BListItem* item = ItemAt(index);
1662 if (item != NULL && item->IsSelected()) {
1663 BRect frame(ItemFrame(index));
1664 BRect bounds(Bounds());
1666 item->Deselect();
1668 if (fFirstSelected == index && fLastSelected == index) {
1669 fFirstSelected = -1;
1670 fLastSelected = -1;
1671 } else {
1672 if (fFirstSelected == index)
1673 fFirstSelected = _CalcFirstSelected(index);
1675 if (fLastSelected == index)
1676 fLastSelected = _CalcLastSelected(index);
1679 if (window && bounds.Intersects(frame))
1680 DrawItem(ItemAt(index), frame, true);
1683 return true;
1687 bool
1688 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1690 if (fFirstSelected == -1)
1691 return false;
1693 BAutolock locker(Window());
1694 if (Window() && !locker.IsLocked())
1695 return false;
1697 bool changed = false;
1699 for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1700 // don't deselect the items we shouldn't deselect
1701 if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1702 continue;
1704 BListItem* item = ItemAt(index);
1705 if (item != NULL && item->IsSelected()) {
1706 item->Deselect();
1707 InvalidateItem(index);
1708 changed = true;
1712 if (!changed)
1713 return false;
1715 if (exceptFrom != -1) {
1716 fFirstSelected = _CalcFirstSelected(exceptFrom);
1717 fLastSelected = _CalcLastSelected(exceptTo);
1718 } else
1719 fFirstSelected = fLastSelected = -1;
1721 return true;
1725 int32
1726 BListView::_CalcFirstSelected(int32 after)
1728 if (after >= CountItems())
1729 return -1;
1731 int32 count = CountItems();
1732 for (int32 i = after; i < count; i++) {
1733 if (ItemAt(i)->IsSelected())
1734 return i;
1737 return -1;
1741 int32
1742 BListView::_CalcLastSelected(int32 before)
1744 if (before < 0)
1745 return -1;
1747 before = std::min(CountItems() - 1, before);
1749 for (int32 i = before; i >= 0; i--) {
1750 if (ItemAt(i)->IsSelected())
1751 return i;
1754 return -1;
1758 void
1759 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
1761 if (!item->IsEnabled()) {
1762 rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
1763 rgb_color disabledColor;
1764 if (textColor.red + textColor.green + textColor.blue > 128 * 3)
1765 disabledColor = tint_color(textColor, B_DARKEN_2_TINT);
1766 else
1767 disabledColor = tint_color(textColor, B_LIGHTEN_2_TINT);
1769 SetHighColor(disabledColor);
1770 } else if (item->IsSelected())
1771 SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
1772 else
1773 SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
1775 item->DrawItem(this, itemRect, complete);
1779 bool
1780 BListView::_SwapItems(int32 a, int32 b)
1782 // remember frames of items before anything happens,
1783 // the tricky situation is when the two items have
1784 // a different height
1785 BRect aFrame = ItemFrame(a);
1786 BRect bFrame = ItemFrame(b);
1788 if (!fList.SwapItems(a, b))
1789 return false;
1791 if (a == b) {
1792 // nothing to do, but success nevertheless
1793 return true;
1796 // track anchor item
1797 if (fAnchorIndex == a)
1798 fAnchorIndex = b;
1799 else if (fAnchorIndex == b)
1800 fAnchorIndex = a;
1802 // track selection
1803 // NOTE: this is only important if the selection status
1804 // of both items is not the same
1805 int32 first = std::min(a, b);
1806 int32 last = std::max(a, b);
1807 if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1808 if (first < fFirstSelected || last > fLastSelected) {
1809 _RescanSelection(std::min(first, fFirstSelected),
1810 std::max(last, fLastSelected));
1812 // though the actually selected items stayed the
1813 // same, the selection has still changed
1814 SelectionChanged();
1817 ItemAt(a)->SetTop(aFrame.top);
1818 ItemAt(b)->SetTop(bFrame.top);
1820 // take care of invalidation
1821 if (Window()) {
1822 // NOTE: window looper is assumed to be locked!
1823 if (aFrame.Height() != bFrame.Height()) {
1824 _RecalcItemTops(first, last);
1825 // items in between shifted visually
1826 Invalidate(aFrame | bFrame);
1827 } else {
1828 Invalidate(aFrame);
1829 Invalidate(bFrame);
1833 return true;
1837 bool
1838 BListView::_MoveItem(int32 from, int32 to)
1840 // remember item frames before doing anything
1841 BRect frameFrom = ItemFrame(from);
1842 BRect frameTo = ItemFrame(to);
1844 if (!fList.MoveItem(from, to))
1845 return false;
1847 // track anchor item
1848 if (fAnchorIndex == from)
1849 fAnchorIndex = to;
1851 // track selection
1852 if (ItemAt(to)->IsSelected()) {
1853 _RescanSelection(from, to);
1854 // though the actually selected items stayed the
1855 // same, the selection has still changed
1856 SelectionChanged();
1859 _RecalcItemTops((to > from) ? from : to);
1861 // take care of invalidation
1862 if (Window()) {
1863 // NOTE: window looper is assumed to be locked!
1864 Invalidate(frameFrom | frameTo);
1867 return true;
1871 bool
1872 BListView::_ReplaceItem(int32 index, BListItem* item)
1874 if (item == NULL)
1875 return false;
1877 BListItem* old = ItemAt(index);
1878 if (!old)
1879 return false;
1881 BRect frame = ItemFrame(index);
1883 bool selectionChanged = old->IsSelected() != item->IsSelected();
1885 // replace item
1886 if (!fList.ReplaceItem(index, item))
1887 return false;
1889 // tack selection
1890 if (selectionChanged) {
1891 int32 start = std::min(fFirstSelected, index);
1892 int32 end = std::max(fLastSelected, index);
1893 _RescanSelection(start, end);
1894 SelectionChanged();
1896 _RecalcItemTops(index);
1898 bool itemHeightChanged = frame != ItemFrame(index);
1900 // take care of invalidation
1901 if (Window()) {
1902 // NOTE: window looper is assumed to be locked!
1903 if (itemHeightChanged)
1904 _InvalidateFrom(index);
1905 else
1906 Invalidate(frame);
1909 if (itemHeightChanged)
1910 _FixupScrollBar();
1912 return true;
1916 void
1917 BListView::_RescanSelection(int32 from, int32 to)
1919 if (from > to) {
1920 int32 tmp = from;
1921 from = to;
1922 to = tmp;
1925 from = std::max((int32)0, from);
1926 to = std::min(to, CountItems() - 1);
1928 if (fAnchorIndex != -1) {
1929 if (fAnchorIndex == from)
1930 fAnchorIndex = to;
1931 else if (fAnchorIndex == to)
1932 fAnchorIndex = from;
1935 for (int32 i = from; i <= to; i++) {
1936 if (ItemAt(i)->IsSelected()) {
1937 fFirstSelected = i;
1938 break;
1942 if (fFirstSelected > from)
1943 from = fFirstSelected;
1945 fLastSelected = fFirstSelected;
1946 for (int32 i = from; i <= to; i++) {
1947 if (ItemAt(i)->IsSelected())
1948 fLastSelected = i;
1953 void
1954 BListView::_RecalcItemTops(int32 start, int32 end)
1956 int32 count = CountItems();
1957 if ((start < 0) || (start >= count))
1958 return;
1960 if (end >= 0)
1961 count = end + 1;
1963 float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
1965 for (int32 i = start; i < count; i++) {
1966 BListItem *item = ItemAt(i);
1967 item->SetTop(top);
1968 top += ceilf(item->Height());
1973 void
1974 BListView::_DoneTracking(BPoint where)
1976 fTrack->try_drag = false;
1977 fTrack->is_dragging = false;
1981 void
1982 BListView::_Track(BPoint where, uint32)
1984 if (fTrack->item_index >= 0 && fTrack->try_drag) {
1985 // initiate a drag if the mouse was moved far enough
1986 BPoint offset = where - fTrack->drag_start;
1987 float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
1988 if (dragDistance >= 5.0f) {
1989 fTrack->try_drag = false;
1990 fTrack->is_dragging = InitiateDrag(fTrack->drag_start,
1991 fTrack->item_index, fTrack->was_selected);
1995 if (!fTrack->is_dragging) {
1996 // do selection only if a drag was not initiated
1997 int32 index = IndexOf(where);
1998 BListItem* item = ItemAt(index);
1999 if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
2000 Select(index, fListType == B_MULTIPLE_SELECTION_LIST
2001 && (modifiers() & B_SHIFT_KEY) != 0);
2002 ScrollToSelection();