BListView: Fix missing update for list items.
[haiku.git] / src / kits / interface / ListView.cpp
blob59f9d40e9fd65651d437029b7357f22fdb2c2f10
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 }
84 BListView::BListView(BRect frame, const char* name, list_view_type type,
85 uint32 resizingMode, uint32 flags)
87 BView(frame, name, resizingMode, flags)
89 _InitObject(type);
93 BListView::BListView(const char* name, list_view_type type, uint32 flags)
95 BView(name, flags)
97 _InitObject(type);
101 BListView::BListView(list_view_type type)
103 BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE)
105 _InitObject(type);
109 BListView::BListView(BMessage* archive)
111 BView(archive)
113 int32 listType;
114 archive->FindInt32("_lv_type", &listType);
115 _InitObject((list_view_type)listType);
117 int32 i = 0;
118 BMessage subData;
119 while (archive->FindMessage("_l_items", i++, &subData) == B_OK) {
120 BArchivable* object = instantiate_object(&subData);
121 if (object == NULL)
122 continue;
124 BListItem* item = dynamic_cast<BListItem*>(object);
125 if (item != NULL)
126 AddItem(item);
129 if (archive->HasMessage("_msg")) {
130 BMessage* invokationMessage = new BMessage;
132 archive->FindMessage("_msg", invokationMessage);
133 SetInvocationMessage(invokationMessage);
136 if (archive->HasMessage("_2nd_msg")) {
137 BMessage* selectionMessage = new BMessage;
139 archive->FindMessage("_2nd_msg", selectionMessage);
140 SetSelectionMessage(selectionMessage);
145 BListView::~BListView()
147 // NOTE: According to BeBook, BListView does not free the items itself.
148 delete fTrack;
149 SetSelectionMessage(NULL);
153 // #pragma mark -
156 BArchivable*
157 BListView::Instantiate(BMessage* archive)
159 if (validate_instantiation(archive, "BListView"))
160 return new BListView(archive);
162 return NULL;
166 status_t
167 BListView::Archive(BMessage* data, bool deep) const
169 status_t status = BView::Archive(data, deep);
170 if (status < B_OK)
171 return status;
173 status = data->AddInt32("_lv_type", fListType);
174 if (status == B_OK && deep) {
175 BListItem* item;
176 int32 i = 0;
178 while ((item = ItemAt(i++)) != NULL) {
179 BMessage subData;
180 status = item->Archive(&subData, true);
181 if (status >= B_OK)
182 status = data->AddMessage("_l_items", &subData);
184 if (status < B_OK)
185 break;
189 if (status >= B_OK && InvocationMessage() != NULL)
190 status = data->AddMessage("_msg", InvocationMessage());
192 if (status == B_OK && fSelectMessage != NULL)
193 status = data->AddMessage("_2nd_msg", fSelectMessage);
195 return status;
199 // #pragma mark -
202 void
203 BListView::Draw(BRect updateRect)
205 int32 count = CountItems();
206 if (count == 0)
207 return;
209 BRect itemFrame(0, 0, Bounds().right, -1);
210 for (int i = 0; i < count; i++) {
211 BListItem* item = ItemAt(i);
212 itemFrame.bottom = itemFrame.top + ceilf(item->Height()) - 1;
214 if (itemFrame.Intersects(updateRect))
215 DrawItem(item, itemFrame);
217 itemFrame.top = itemFrame.bottom + 1;
222 void
223 BListView::AttachedToWindow()
225 BView::AttachedToWindow();
226 _FontChanged();
228 if (!Messenger().IsValid())
229 SetTarget(Window(), NULL);
231 _FixupScrollBar();
235 void
236 BListView::DetachedFromWindow()
238 BView::DetachedFromWindow();
242 void
243 BListView::AllAttached()
245 BView::AllAttached();
249 void
250 BListView::AllDetached()
252 BView::AllDetached();
256 void
257 BListView::FrameResized(float newWidth, float newHeight)
259 _FixupScrollBar();
261 // notify items of new width.
262 _FontChanged();
266 void
267 BListView::FrameMoved(BPoint newPosition)
269 BView::FrameMoved(newPosition);
273 void
274 BListView::TargetedByScrollView(BScrollView* view)
276 fScrollView = view;
277 // TODO: We could SetFlags(Flags() | B_FRAME_EVENTS) here, but that
278 // may mess up application code which manages this by some other means
279 // and doesn't want us to be messing with flags.
283 void
284 BListView::WindowActivated(bool active)
286 BView::WindowActivated(active);
290 // #pragma mark -
293 void
294 BListView::MessageReceived(BMessage* message)
296 switch (message->what) {
297 case B_MOUSE_WHEEL_CHANGED:
298 if (!fTrack->is_dragging)
299 BView::MessageReceived(message);
300 break;
302 case B_COUNT_PROPERTIES:
303 case B_EXECUTE_PROPERTY:
304 case B_GET_PROPERTY:
305 case B_SET_PROPERTY:
307 BPropertyInfo propInfo(sProperties);
308 BMessage specifier;
309 const char* property;
311 if (message->GetCurrentSpecifier(NULL, &specifier) != B_OK
312 || specifier.FindString("property", &property) != B_OK) {
313 return;
316 switch (propInfo.FindMatch(message, 0, &specifier, message->what,
317 property)) {
318 case B_ERROR:
319 BView::MessageReceived(message);
320 break;
322 case 0:
324 BMessage reply(B_REPLY);
325 reply.AddInt32("result", CountItems());
326 reply.AddInt32("error", B_OK);
328 message->SendReply(&reply);
329 break;
332 case 1:
333 break;
335 case 2:
337 int32 count = 0;
339 for (int32 i = 0; i < CountItems(); i++) {
340 if (ItemAt(i)->IsSelected())
341 count++;
344 BMessage reply(B_REPLY);
345 reply.AddInt32("result", count);
346 reply.AddInt32("error", B_OK);
348 message->SendReply(&reply);
349 break;
352 case 3:
353 break;
355 case 4:
357 BMessage reply (B_REPLY);
359 for (int32 i = 0; i < CountItems(); i++) {
360 if (ItemAt(i)->IsSelected())
361 reply.AddInt32("result", i);
364 reply.AddInt32("error", B_OK);
366 message->SendReply(&reply);
367 break;
370 case 5:
371 break;
373 case 6:
375 BMessage reply(B_REPLY);
377 bool select;
378 if (message->FindBool("data", &select) == B_OK && select)
379 Select(0, CountItems() - 1, false);
380 else
381 DeselectAll();
383 reply.AddInt32("error", B_OK);
385 message->SendReply(&reply);
386 break;
389 break;
392 case B_SELECT_ALL:
393 if (fListType == B_MULTIPLE_SELECTION_LIST)
394 Select(0, CountItems() - 1, false);
395 break;
397 default:
398 BView::MessageReceived(message);
403 void
404 BListView::KeyDown(const char* bytes, int32 numBytes)
406 bool extend = fListType == B_MULTIPLE_SELECTION_LIST
407 && (modifiers() & B_SHIFT_KEY) != 0;
409 if (fFirstSelected == -1
410 && (bytes[0] == B_UP_ARROW || bytes[0] == B_DOWN_ARROW)) {
411 // nothing is selected yet, select the first enabled item
412 int32 lastItem = CountItems() - 1;
413 for (int32 i = 0; i <= lastItem; i++) {
414 if (ItemAt(i)->IsEnabled()) {
415 Select(i);
416 break;
419 return;
422 switch (bytes[0]) {
423 case B_UP_ARROW:
425 if (fAnchorIndex > 0) {
426 if (!extend || fAnchorIndex <= fFirstSelected) {
427 for (int32 i = 1; fAnchorIndex - i >= 0; i++) {
428 if (ItemAt(fAnchorIndex - i)->IsEnabled()) {
429 // Select the previous enabled item
430 Select(fAnchorIndex - i, extend);
431 break;
434 } else {
435 Deselect(fAnchorIndex);
437 fAnchorIndex--;
438 while (fAnchorIndex > 0
439 && !ItemAt(fAnchorIndex)->IsEnabled());
443 ScrollToSelection();
444 break;
447 case B_DOWN_ARROW:
449 int32 lastItem = CountItems() - 1;
450 if (fAnchorIndex < lastItem) {
451 if (!extend || fAnchorIndex >= fLastSelected) {
452 for (int32 i = 1; fAnchorIndex + i <= lastItem; i++) {
453 if (ItemAt(fAnchorIndex + i)->IsEnabled()) {
454 // Select the next enabled item
455 Select(fAnchorIndex + i, extend);
456 break;
459 } else {
460 Deselect(fAnchorIndex);
462 fAnchorIndex++;
463 while (fAnchorIndex < lastItem
464 && !ItemAt(fAnchorIndex)->IsEnabled());
468 ScrollToSelection();
469 break;
472 case B_HOME:
473 if (extend) {
474 Select(0, fAnchorIndex, true);
475 fAnchorIndex = 0;
476 } else {
477 // select the first enabled item
478 int32 lastItem = CountItems() - 1;
479 for (int32 i = 0; i <= lastItem; i++) {
480 if (ItemAt(i)->IsEnabled()) {
481 Select(i, false);
482 break;
487 ScrollToSelection();
488 break;
490 case B_END:
491 if (extend) {
492 Select(fAnchorIndex, CountItems() - 1, true);
493 fAnchorIndex = CountItems() - 1;
494 } else {
495 // select the last enabled item
496 for (int32 i = CountItems() - 1; i >= 0; i--) {
497 if (ItemAt(i)->IsEnabled()) {
498 Select(i, false);
499 break;
504 ScrollToSelection();
505 break;
507 case B_PAGE_UP:
509 BPoint scrollOffset(LeftTop());
510 scrollOffset.y = std::max(0.0f, scrollOffset.y - Bounds().Height());
511 ScrollTo(scrollOffset);
512 break;
515 case B_PAGE_DOWN:
517 BPoint scrollOffset(LeftTop());
518 if (BListItem* item = LastItem()) {
519 scrollOffset.y += Bounds().Height();
520 scrollOffset.y = std::min(item->Bottom() - Bounds().Height(),
521 scrollOffset.y);
523 ScrollTo(scrollOffset);
524 break;
527 case B_RETURN:
528 case B_SPACE:
529 Invoke();
530 break;
532 default:
533 BView::KeyDown(bytes, numBytes);
538 void
539 BListView::MouseDown(BPoint where)
541 if (!IsFocus()) {
542 MakeFocus();
543 Sync();
544 Window()->UpdateIfNeeded();
547 BMessage* message = Looper()->CurrentMessage();
548 int32 index = IndexOf(where);
550 int32 buttons = 0;
551 if (message != NULL)
552 message->FindInt32("buttons", &buttons);
554 int32 modifiers = 0;
555 if (message != NULL)
556 message->FindInt32("modifiers", &modifiers);
558 // If the user double (or more) clicked within the current selection,
559 // we don't change the selection but invoke the selection.
560 // TODO: move this code someplace where it can be shared everywhere
561 // instead of every class having to reimplement it, once some sane
562 // API for it is decided.
563 BPoint delta = where - fTrack->drag_start;
564 bigtime_t sysTime;
565 Window()->CurrentMessage()->FindInt64("when", &sysTime);
566 bigtime_t timeDelta = sysTime - fTrack->last_click_time;
567 bigtime_t doubleClickSpeed;
568 get_click_speed(&doubleClickSpeed);
569 bool doubleClick = false;
571 if (timeDelta < doubleClickSpeed
572 && fabs(delta.x) < kDoubleClickThreshold
573 && fabs(delta.y) < kDoubleClickThreshold
574 && fTrack->item_index == index) {
575 doubleClick = true;
578 if (doubleClick && index >= fFirstSelected && index <= fLastSelected) {
579 fTrack->drag_start.Set(INT32_MAX, INT32_MAX);
580 Invoke();
581 return BView::MouseDown(where);
584 if (!doubleClick) {
585 fTrack->drag_start = where;
586 fTrack->last_click_time = system_time();
587 fTrack->item_index = index;
588 fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false;
589 fTrack->try_drag = true;
591 MouseDownThread<BListView>::TrackMouse(this,
592 &BListView::_DoneTracking, &BListView::_Track);
595 if (index >= 0) {
596 if (fListType == B_MULTIPLE_SELECTION_LIST) {
597 if ((modifiers & B_SHIFT_KEY) != 0) {
598 // select entire block
599 // TODO: maybe review if we want it like in Tracker
600 // (anchor item)
601 if (index >= fFirstSelected && index < fLastSelected) {
602 // clicked inside of selected items block, deselect all
603 // but from the first selected item to the clicked item
604 DeselectExcept(fFirstSelected, index);
605 } else {
606 Select(std::min(index, fFirstSelected), std::max(index,
607 fLastSelected));
609 } else {
610 if ((modifiers & B_COMMAND_KEY) != 0) {
611 // toggle selection state of clicked item (like in Tracker)
612 // toggle selection state of clicked item
613 if (ItemAt(index)->IsSelected())
614 Deselect(index);
615 else
616 Select(index, true);
617 } else
618 Select(index);
620 } else {
621 // toggle selection state of clicked item
622 if ((modifiers & B_COMMAND_KEY) != 0 && ItemAt(index)->IsSelected())
623 Deselect(index);
624 else
625 Select(index);
627 } else if ((modifiers & B_COMMAND_KEY) == 0)
628 DeselectAll();
630 BView::MouseDown(where);
634 void
635 BListView::MouseUp(BPoint where)
637 BView::MouseUp(where);
641 void
642 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
644 BView::MouseMoved(where, code, dragMessage);
648 bool
649 BListView::InitiateDrag(BPoint where, int32 index, bool wasSelected)
651 return false;
655 // #pragma mark -
658 void
659 BListView::ResizeToPreferred()
661 BView::ResizeToPreferred();
665 void
666 BListView::GetPreferredSize(float *_width, float *_height)
668 int32 count = CountItems();
670 if (count > 0) {
671 float maxWidth = 0.0;
672 for (int32 i = 0; i < count; i++) {
673 float itemWidth = ItemAt(i)->Width();
674 if (itemWidth > maxWidth)
675 maxWidth = itemWidth;
678 if (_width != NULL)
679 *_width = maxWidth;
680 if (_height != NULL)
681 *_height = ItemAt(count - 1)->Bottom();
682 } else
683 BView::GetPreferredSize(_width, _height);
687 BSize
688 BListView::MinSize()
690 // We need a stable min size: the BView implementation uses
691 // GetPreferredSize(), which by default just returns the current size.
692 return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
696 BSize
697 BListView::MaxSize()
699 return BView::MaxSize();
703 BSize
704 BListView::PreferredSize()
706 // We need a stable preferred size: the BView implementation uses
707 // GetPreferredSize(), which by default just returns the current size.
708 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
712 // #pragma mark -
715 void
716 BListView::MakeFocus(bool focused)
718 if (IsFocus() == focused)
719 return;
721 BView::MakeFocus(focused);
723 if (fScrollView)
724 fScrollView->SetBorderHighlighted(focused);
728 void
729 BListView::SetFont(const BFont* font, uint32 mask)
731 BView::SetFont(font, mask);
733 if (Window() != NULL && !Window()->InViewTransaction())
734 _FontChanged();
738 void
739 BListView::ScrollTo(BPoint point)
741 BView::ScrollTo(point);
745 // #pragma mark - List ops
748 bool
749 BListView::AddItem(BListItem* item, int32 index)
751 if (!fList.AddItem(item, index))
752 return false;
754 if (fFirstSelected != -1 && index <= fFirstSelected)
755 fFirstSelected++;
757 if (fLastSelected != -1 && index <= fLastSelected)
758 fLastSelected++;
760 if (Window()) {
761 BFont font;
762 GetFont(&font);
763 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
765 item->Update(this, &font);
766 _RecalcItemTops(index + 1);
768 _FixupScrollBar();
769 _InvalidateFrom(index);
772 return true;
776 bool
777 BListView::AddItem(BListItem* item)
779 if (!fList.AddItem(item))
780 return false;
782 // No need to adapt selection, as this item is the last in the list
784 if (Window()) {
785 BFont font;
786 GetFont(&font);
787 int32 index = CountItems() - 1;
788 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
790 item->Update(this, &font);
792 _FixupScrollBar();
793 InvalidateItem(CountItems() - 1);
796 return true;
800 bool
801 BListView::AddList(BList* list, int32 index)
803 if (!fList.AddList(list, index))
804 return false;
806 int32 count = list->CountItems();
808 if (fFirstSelected != -1 && index < fFirstSelected)
809 fFirstSelected += count;
811 if (fLastSelected != -1 && index < fLastSelected)
812 fLastSelected += count;
814 if (Window()) {
815 BFont font;
816 GetFont(&font);
818 for (int32 i = index; i <= (index + count - 1); i++) {
819 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
820 ItemAt(i)->Update(this, &font);
823 _RecalcItemTops(index + count - 1);
825 _FixupScrollBar();
826 Invalidate(); // TODO
829 return true;
833 bool
834 BListView::AddList(BList* list)
836 return AddList(list, CountItems());
840 BListItem*
841 BListView::RemoveItem(int32 index)
843 BListItem* item = ItemAt(index);
844 if (item == NULL)
845 return NULL;
847 if (item->IsSelected())
848 Deselect(index);
850 if (!fList.RemoveItem(item))
851 return NULL;
853 if (fFirstSelected != -1 && index < fFirstSelected)
854 fFirstSelected--;
856 if (fLastSelected != -1 && index < fLastSelected)
857 fLastSelected--;
859 if (fAnchorIndex != -1 && index < fAnchorIndex)
860 fAnchorIndex--;
862 _RecalcItemTops(index);
864 _InvalidateFrom(index);
865 _FixupScrollBar();
867 return item;
871 bool
872 BListView::RemoveItem(BListItem* item)
874 return BListView::RemoveItem(IndexOf(item)) != NULL;
878 bool
879 BListView::RemoveItems(int32 index, int32 count)
881 if (index >= fList.CountItems())
882 index = -1;
884 if (index < 0)
885 return false;
887 if (fAnchorIndex != -1 && index < fAnchorIndex)
888 fAnchorIndex = index;
890 fList.RemoveItems(index, count);
891 if (index < fList.CountItems())
892 _RecalcItemTops(index);
894 Invalidate();
895 return true;
899 void
900 BListView::SetSelectionMessage(BMessage* message)
902 delete fSelectMessage;
903 fSelectMessage = message;
907 void
908 BListView::SetInvocationMessage(BMessage* message)
910 BInvoker::SetMessage(message);
914 BMessage*
915 BListView::InvocationMessage() const
917 return BInvoker::Message();
921 uint32
922 BListView::InvocationCommand() const
924 return BInvoker::Command();
928 BMessage*
929 BListView::SelectionMessage() const
931 return fSelectMessage;
935 uint32
936 BListView::SelectionCommand() const
938 if (fSelectMessage)
939 return fSelectMessage->what;
941 return 0;
945 void
946 BListView::SetListType(list_view_type type)
948 if (fListType == B_MULTIPLE_SELECTION_LIST
949 && type == B_SINGLE_SELECTION_LIST) {
950 Select(CurrentSelection(0));
953 fListType = type;
957 list_view_type
958 BListView::ListType() const
960 return fListType;
964 BListItem*
965 BListView::ItemAt(int32 index) const
967 return (BListItem*)fList.ItemAt(index);
971 int32
972 BListView::IndexOf(BListItem* item) const
974 if (Window()) {
975 if (item != NULL) {
976 int32 index = IndexOf(BPoint(0.0, item->Top()));
977 if (index >= 0 && fList.ItemAt(index) == item)
978 return index;
980 return -1;
983 return fList.IndexOf(item);
987 int32
988 BListView::IndexOf(BPoint point) const
990 int32 low = 0;
991 int32 high = fList.CountItems() - 1;
992 int32 mid = -1;
993 float frameTop = -1.0;
994 float frameBottom = 1.0;
996 // binary search the list
997 while (high >= low) {
998 mid = (low + high) / 2;
999 frameTop = ItemAt(mid)->Top();
1000 frameBottom = ItemAt(mid)->Bottom();
1001 if (point.y < frameTop)
1002 high = mid - 1;
1003 else if (point.y > frameBottom)
1004 low = mid + 1;
1005 else
1006 return mid;
1009 return -1;
1013 BListItem*
1014 BListView::FirstItem() const
1016 return (BListItem*)fList.FirstItem();
1020 BListItem*
1021 BListView::LastItem() const
1023 return (BListItem*)fList.LastItem();
1027 bool
1028 BListView::HasItem(BListItem *item) const
1030 return IndexOf(item) != -1;
1034 int32
1035 BListView::CountItems() const
1037 return fList.CountItems();
1041 void
1042 BListView::MakeEmpty()
1044 if (fList.IsEmpty())
1045 return;
1047 _DeselectAll(-1, -1);
1048 fList.MakeEmpty();
1050 if (Window()) {
1051 _FixupScrollBar();
1052 Invalidate();
1057 bool
1058 BListView::IsEmpty() const
1060 return fList.IsEmpty();
1064 void
1065 BListView::DoForEach(bool (*func)(BListItem*))
1067 fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
1071 void
1072 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg)
1074 fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
1078 const BListItem**
1079 BListView::Items() const
1081 return (const BListItem**)fList.Items();
1085 void
1086 BListView::InvalidateItem(int32 index)
1088 Invalidate(ItemFrame(index));
1092 void
1093 BListView::ScrollToSelection()
1095 BRect itemFrame = ItemFrame(CurrentSelection(0));
1097 if (Bounds().Contains(itemFrame))
1098 return;
1100 float scrollPos = itemFrame.top < Bounds().top ?
1101 itemFrame.top : itemFrame.bottom - Bounds().Height();
1103 if (itemFrame.top - scrollPos < Bounds().top)
1104 scrollPos = itemFrame.top;
1106 ScrollTo(itemFrame.left, scrollPos);
1110 void
1111 BListView::Select(int32 index, bool extend)
1113 if (_Select(index, extend)) {
1114 SelectionChanged();
1115 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1120 void
1121 BListView::Select(int32 start, int32 finish, bool extend)
1123 if (_Select(start, finish, extend)) {
1124 SelectionChanged();
1125 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1130 bool
1131 BListView::IsItemSelected(int32 index) const
1133 BListItem* item = ItemAt(index);
1134 if (item != NULL)
1135 return item->IsSelected();
1137 return false;
1141 int32
1142 BListView::CurrentSelection(int32 index) const
1144 if (fFirstSelected == -1)
1145 return -1;
1147 if (index == 0)
1148 return fFirstSelected;
1150 for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1151 if (ItemAt(i)->IsSelected()) {
1152 if (index == 0)
1153 return i;
1155 index--;
1159 return -1;
1163 status_t
1164 BListView::Invoke(BMessage* message)
1166 // Note, this is more or less a copy of BControl::Invoke() and should
1167 // stay that way (ie. changes done there should be adopted here)
1169 bool notify = false;
1170 uint32 kind = InvokeKind(&notify);
1172 BMessage clone(kind);
1173 status_t err = B_BAD_VALUE;
1175 if (!message && !notify)
1176 message = Message();
1178 if (!message) {
1179 if (!IsWatched())
1180 return err;
1181 } else
1182 clone = *message;
1184 clone.AddInt64("when", (int64)system_time());
1185 clone.AddPointer("source", this);
1186 clone.AddMessenger("be:sender", BMessenger(this));
1188 if (fListType == B_SINGLE_SELECTION_LIST)
1189 clone.AddInt32("index", fFirstSelected);
1190 else {
1191 if (fFirstSelected >= 0) {
1192 for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1193 if (ItemAt(i)->IsSelected())
1194 clone.AddInt32("index", i);
1199 if (message)
1200 err = BInvoker::Invoke(&clone);
1202 SendNotices(kind, &clone);
1204 return err;
1208 void
1209 BListView::DeselectAll()
1211 if (_DeselectAll(-1, -1)) {
1212 SelectionChanged();
1213 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1218 void
1219 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
1221 if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
1222 return;
1224 if (_DeselectAll(exceptFrom, exceptTo)) {
1225 SelectionChanged();
1226 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1231 void
1232 BListView::Deselect(int32 index)
1234 if (_Deselect(index)) {
1235 SelectionChanged();
1236 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1241 void
1242 BListView::SelectionChanged()
1244 // Hook method to be implemented by subclasses
1248 void
1249 BListView::SortItems(int (*cmp)(const void *, const void *))
1251 if (_DeselectAll(-1, -1)) {
1252 SelectionChanged();
1253 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1256 fList.SortItems(cmp);
1257 _RecalcItemTops(0);
1258 Invalidate();
1262 bool
1263 BListView::SwapItems(int32 a, int32 b)
1265 MiscData data;
1267 data.swap.a = a;
1268 data.swap.b = b;
1270 return DoMiscellaneous(B_SWAP_OP, &data);
1274 bool
1275 BListView::MoveItem(int32 from, int32 to)
1277 MiscData data;
1279 data.move.from = from;
1280 data.move.to = to;
1282 return DoMiscellaneous(B_MOVE_OP, &data);
1286 bool
1287 BListView::ReplaceItem(int32 index, BListItem* item)
1289 MiscData data;
1291 data.replace.index = index;
1292 data.replace.item = item;
1294 return DoMiscellaneous(B_REPLACE_OP, &data);
1298 BRect
1299 BListView::ItemFrame(int32 index)
1301 BRect frame = Bounds();
1302 if (index < 0 || index >= CountItems()) {
1303 frame.top = 0;
1304 frame.bottom = -1;
1305 } else {
1306 BListItem* item = ItemAt(index);
1307 frame.top = item->Top();
1308 frame.bottom = item->Bottom();
1310 return frame;
1314 // #pragma mark -
1317 BHandler*
1318 BListView::ResolveSpecifier(BMessage* message, int32 index,
1319 BMessage* specifier, int32 what, const char* property)
1321 BPropertyInfo propInfo(sProperties);
1323 if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) {
1324 return BView::ResolveSpecifier(message, index, specifier, what,
1325 property);
1328 // TODO: msg->AddInt32("_match_code_", );
1330 return this;
1334 status_t
1335 BListView::GetSupportedSuites(BMessage* data)
1337 if (data == NULL)
1338 return B_BAD_VALUE;
1340 status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
1342 BPropertyInfo propertyInfo(sProperties);
1343 if (err == B_OK)
1344 err = data->AddFlat("messages", &propertyInfo);
1346 if (err == B_OK)
1347 return BView::GetSupportedSuites(data);
1348 return err;
1352 status_t
1353 BListView::Perform(perform_code code, void* _data)
1355 switch (code) {
1356 case PERFORM_CODE_MIN_SIZE:
1357 ((perform_data_min_size*)_data)->return_value
1358 = BListView::MinSize();
1359 return B_OK;
1360 case PERFORM_CODE_MAX_SIZE:
1361 ((perform_data_max_size*)_data)->return_value
1362 = BListView::MaxSize();
1363 return B_OK;
1364 case PERFORM_CODE_PREFERRED_SIZE:
1365 ((perform_data_preferred_size*)_data)->return_value
1366 = BListView::PreferredSize();
1367 return B_OK;
1368 case PERFORM_CODE_LAYOUT_ALIGNMENT:
1369 ((perform_data_layout_alignment*)_data)->return_value
1370 = BListView::LayoutAlignment();
1371 return B_OK;
1372 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1373 ((perform_data_has_height_for_width*)_data)->return_value
1374 = BListView::HasHeightForWidth();
1375 return B_OK;
1376 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1378 perform_data_get_height_for_width* data
1379 = (perform_data_get_height_for_width*)_data;
1380 BListView::GetHeightForWidth(data->width, &data->min, &data->max,
1381 &data->preferred);
1382 return B_OK;
1384 case PERFORM_CODE_SET_LAYOUT:
1386 perform_data_set_layout* data = (perform_data_set_layout*)_data;
1387 BListView::SetLayout(data->layout);
1388 return B_OK;
1390 case PERFORM_CODE_LAYOUT_INVALIDATED:
1392 perform_data_layout_invalidated* data
1393 = (perform_data_layout_invalidated*)_data;
1394 BListView::LayoutInvalidated(data->descendants);
1395 return B_OK;
1397 case PERFORM_CODE_DO_LAYOUT:
1399 BListView::DoLayout();
1400 return B_OK;
1404 return BView::Perform(code, _data);
1408 bool
1409 BListView::DoMiscellaneous(MiscCode code, MiscData* data)
1411 if (code > B_SWAP_OP)
1412 return false;
1414 switch (code) {
1415 case B_NO_OP:
1416 break;
1418 case B_REPLACE_OP:
1419 return _ReplaceItem(data->replace.index, data->replace.item);
1421 case B_MOVE_OP:
1422 return _MoveItem(data->move.from, data->move.to);
1424 case B_SWAP_OP:
1425 return _SwapItems(data->swap.a, data->swap.b);
1428 return false;
1432 // #pragma mark -
1435 void BListView::_ReservedListView2() {}
1436 void BListView::_ReservedListView3() {}
1437 void BListView::_ReservedListView4() {}
1440 BListView&
1441 BListView::operator=(const BListView& /*other*/)
1443 return *this;
1447 // #pragma mark -
1450 void
1451 BListView::_InitObject(list_view_type type)
1453 fListType = type;
1454 fFirstSelected = -1;
1455 fLastSelected = -1;
1456 fAnchorIndex = -1;
1457 fSelectMessage = NULL;
1458 fScrollView = NULL;
1460 fTrack = new track_data;
1461 fTrack->drag_start = B_ORIGIN;
1462 fTrack->item_index = -1;
1463 fTrack->was_selected = false;
1464 fTrack->try_drag = false;
1465 fTrack->is_dragging = false;
1466 fTrack->last_click_time = 0;
1468 SetViewUIColor(B_LIST_BACKGROUND_COLOR);
1469 SetLowUIColor(B_LIST_BACKGROUND_COLOR);
1473 void
1474 BListView::_FixupScrollBar()
1476 BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1477 if (!vertScroller)
1478 return;
1480 BRect bounds = Bounds();
1481 int32 count = CountItems();
1483 float itemHeight = 0.0;
1485 if (CountItems() > 0)
1486 itemHeight = ItemAt(CountItems() - 1)->Bottom();
1488 if (bounds.Height() > itemHeight) {
1489 // no scrolling
1490 vertScroller->SetRange(0.0, 0.0);
1491 vertScroller->SetValue(0.0);
1492 // also scrolls ListView to the top
1493 } else {
1494 vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1495 vertScroller->SetProportion(bounds.Height () / itemHeight);
1496 // scroll up if there is empty room on bottom
1497 if (itemHeight < bounds.bottom)
1498 ScrollBy(0.0, bounds.bottom - itemHeight);
1501 if (count != 0)
1502 vertScroller->SetSteps(ceilf(FirstItem()->Height()), bounds.Height());
1506 void
1507 BListView::_InvalidateFrom(int32 index)
1509 // make sure index is behind last valid index
1510 int32 count = CountItems();
1511 if (index >= count)
1512 index = count;
1514 // take the item before the wanted one,
1515 // because that might already be removed
1516 index--;
1517 BRect dirty = Bounds();
1518 if (index >= 0)
1519 dirty.top = ItemFrame(index).bottom + 1;
1521 Invalidate(dirty);
1525 void
1526 BListView::_FontChanged()
1528 BFont font;
1529 GetFont(&font);
1530 for (int32 i = 0; i < CountItems(); i++) {
1531 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
1532 ItemAt(i)->Update(this, &font);
1537 /*! Selects the item at the specified \a index, and returns \c true in
1538 case the selection was changed because of this method.
1539 If \a extend is \c false, all previously selected items are deselected.
1541 bool
1542 BListView::_Select(int32 index, bool extend)
1544 if (index < 0 || index >= CountItems())
1545 return false;
1547 // only lock the window when there is one
1548 BAutolock locker(Window());
1549 if (Window() != NULL && !locker.IsLocked())
1550 return false;
1552 bool changed = false;
1554 if (!extend && fFirstSelected != -1)
1555 changed = _DeselectAll(index, index);
1557 fAnchorIndex = index;
1559 BListItem* item = ItemAt(index);
1560 if (!item->IsEnabled() || item->IsSelected()) {
1561 // if the item is already selected, or can't be selected,
1562 // we're done here
1563 return changed;
1566 // keep track of first and last selected item
1567 if (fFirstSelected == -1) {
1568 // no previous selection
1569 fFirstSelected = index;
1570 fLastSelected = index;
1571 } else if (index < fFirstSelected) {
1572 fFirstSelected = index;
1573 } else if (index > fLastSelected) {
1574 fLastSelected = index;
1577 item->Select();
1578 if (Window() != NULL)
1579 InvalidateItem(index);
1581 return true;
1586 Selects the items between \a from and \a to, and returns \c true in
1587 case the selection was changed because of this method.
1588 If \a extend is \c false, all previously selected items are deselected.
1590 bool
1591 BListView::_Select(int32 from, int32 to, bool extend)
1593 if (to < from)
1594 return false;
1596 BAutolock locker(Window());
1597 if (Window() && !locker.IsLocked())
1598 return false;
1600 bool changed = false;
1602 if (fFirstSelected != -1 && !extend)
1603 changed = _DeselectAll(from, to);
1605 if (fFirstSelected == -1) {
1606 fFirstSelected = from;
1607 fLastSelected = to;
1608 } else {
1609 if (from < fFirstSelected)
1610 fFirstSelected = from;
1611 if (to > fLastSelected)
1612 fLastSelected = to;
1615 for (int32 i = from; i <= to; ++i) {
1616 BListItem* item = ItemAt(i);
1617 if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
1618 item->Select();
1619 if (Window() != NULL)
1620 InvalidateItem(i);
1621 changed = true;
1625 return changed;
1629 bool
1630 BListView::_Deselect(int32 index)
1632 if (index < 0 || index >= CountItems())
1633 return false;
1635 BWindow* window = Window();
1636 BAutolock locker(window);
1637 if (window != NULL && !locker.IsLocked())
1638 return false;
1640 BListItem* item = ItemAt(index);
1642 if (item != NULL && item->IsSelected()) {
1643 BRect frame(ItemFrame(index));
1644 BRect bounds(Bounds());
1646 item->Deselect();
1648 if (fFirstSelected == index && fLastSelected == index) {
1649 fFirstSelected = -1;
1650 fLastSelected = -1;
1651 } else {
1652 if (fFirstSelected == index)
1653 fFirstSelected = _CalcFirstSelected(index);
1655 if (fLastSelected == index)
1656 fLastSelected = _CalcLastSelected(index);
1659 if (window && bounds.Intersects(frame))
1660 DrawItem(ItemAt(index), frame, true);
1663 return true;
1667 bool
1668 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1670 if (fFirstSelected == -1)
1671 return false;
1673 BAutolock locker(Window());
1674 if (Window() && !locker.IsLocked())
1675 return false;
1677 bool changed = false;
1679 for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1680 // don't deselect the items we shouldn't deselect
1681 if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1682 continue;
1684 BListItem* item = ItemAt(index);
1685 if (item != NULL && item->IsSelected()) {
1686 item->Deselect();
1687 InvalidateItem(index);
1688 changed = true;
1692 if (!changed)
1693 return false;
1695 if (exceptFrom != -1) {
1696 fFirstSelected = _CalcFirstSelected(exceptFrom);
1697 fLastSelected = _CalcLastSelected(exceptTo);
1698 } else
1699 fFirstSelected = fLastSelected = -1;
1701 return true;
1705 int32
1706 BListView::_CalcFirstSelected(int32 after)
1708 if (after >= CountItems())
1709 return -1;
1711 int32 count = CountItems();
1712 for (int32 i = after; i < count; i++) {
1713 if (ItemAt(i)->IsSelected())
1714 return i;
1717 return -1;
1721 int32
1722 BListView::_CalcLastSelected(int32 before)
1724 if (before < 0)
1725 return -1;
1727 before = std::min(CountItems() - 1, before);
1729 for (int32 i = before; i >= 0; i--) {
1730 if (ItemAt(i)->IsSelected())
1731 return i;
1734 return -1;
1738 void
1739 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
1741 if (!item->IsEnabled()) {
1742 rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
1743 rgb_color disabledColor;
1744 if (textColor.red + textColor.green + textColor.blue > 128 * 3)
1745 disabledColor = tint_color(textColor, B_DARKEN_2_TINT);
1746 else
1747 disabledColor = tint_color(textColor, B_LIGHTEN_2_TINT);
1749 SetHighColor(disabledColor);
1750 } else if (item->IsSelected())
1751 SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
1752 else
1753 SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
1755 item->DrawItem(this, itemRect, complete);
1759 bool
1760 BListView::_SwapItems(int32 a, int32 b)
1762 // remember frames of items before anything happens,
1763 // the tricky situation is when the two items have
1764 // a different height
1765 BRect aFrame = ItemFrame(a);
1766 BRect bFrame = ItemFrame(b);
1768 if (!fList.SwapItems(a, b))
1769 return false;
1771 if (a == b) {
1772 // nothing to do, but success nevertheless
1773 return true;
1776 // track anchor item
1777 if (fAnchorIndex == a)
1778 fAnchorIndex = b;
1779 else if (fAnchorIndex == b)
1780 fAnchorIndex = a;
1782 // track selection
1783 // NOTE: this is only important if the selection status
1784 // of both items is not the same
1785 int32 first = std::min(a, b);
1786 int32 last = std::max(a, b);
1787 if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1788 if (first < fFirstSelected || last > fLastSelected) {
1789 _RescanSelection(std::min(first, fFirstSelected),
1790 std::max(last, fLastSelected));
1792 // though the actually selected items stayed the
1793 // same, the selection has still changed
1794 SelectionChanged();
1797 ItemAt(a)->SetTop(aFrame.top);
1798 ItemAt(b)->SetTop(bFrame.top);
1800 // take care of invalidation
1801 if (Window()) {
1802 // NOTE: window looper is assumed to be locked!
1803 if (aFrame.Height() != bFrame.Height()) {
1804 _RecalcItemTops(first, last);
1805 // items in between shifted visually
1806 Invalidate(aFrame | bFrame);
1807 } else {
1808 Invalidate(aFrame);
1809 Invalidate(bFrame);
1813 return true;
1817 bool
1818 BListView::_MoveItem(int32 from, int32 to)
1820 // remember item frames before doing anything
1821 BRect frameFrom = ItemFrame(from);
1822 BRect frameTo = ItemFrame(to);
1824 if (!fList.MoveItem(from, to))
1825 return false;
1827 // track anchor item
1828 if (fAnchorIndex == from)
1829 fAnchorIndex = to;
1831 // track selection
1832 if (ItemAt(to)->IsSelected()) {
1833 _RescanSelection(from, to);
1834 // though the actually selected items stayed the
1835 // same, the selection has still changed
1836 SelectionChanged();
1839 _RecalcItemTops((to > from) ? from : to);
1841 // take care of invalidation
1842 if (Window()) {
1843 // NOTE: window looper is assumed to be locked!
1844 Invalidate(frameFrom | frameTo);
1847 return true;
1851 bool
1852 BListView::_ReplaceItem(int32 index, BListItem* item)
1854 if (item == NULL)
1855 return false;
1857 BListItem* old = ItemAt(index);
1858 if (!old)
1859 return false;
1861 BRect frame = ItemFrame(index);
1863 bool selectionChanged = old->IsSelected() != item->IsSelected();
1865 // replace item
1866 if (!fList.ReplaceItem(index, item))
1867 return false;
1869 // tack selection
1870 if (selectionChanged) {
1871 int32 start = std::min(fFirstSelected, index);
1872 int32 end = std::max(fLastSelected, index);
1873 _RescanSelection(start, end);
1874 SelectionChanged();
1876 _RecalcItemTops(index);
1878 bool itemHeightChanged = frame != ItemFrame(index);
1880 // take care of invalidation
1881 if (Window()) {
1882 // NOTE: window looper is assumed to be locked!
1883 if (itemHeightChanged)
1884 _InvalidateFrom(index);
1885 else
1886 Invalidate(frame);
1889 if (itemHeightChanged)
1890 _FixupScrollBar();
1892 return true;
1896 void
1897 BListView::_RescanSelection(int32 from, int32 to)
1899 if (from > to) {
1900 int32 tmp = from;
1901 from = to;
1902 to = tmp;
1905 from = std::max((int32)0, from);
1906 to = std::min(to, CountItems() - 1);
1908 if (fAnchorIndex != -1) {
1909 if (fAnchorIndex == from)
1910 fAnchorIndex = to;
1911 else if (fAnchorIndex == to)
1912 fAnchorIndex = from;
1915 for (int32 i = from; i <= to; i++) {
1916 if (ItemAt(i)->IsSelected()) {
1917 fFirstSelected = i;
1918 break;
1922 if (fFirstSelected > from)
1923 from = fFirstSelected;
1925 fLastSelected = fFirstSelected;
1926 for (int32 i = from; i <= to; i++) {
1927 if (ItemAt(i)->IsSelected())
1928 fLastSelected = i;
1933 void
1934 BListView::_RecalcItemTops(int32 start, int32 end)
1936 int32 count = CountItems();
1937 if ((start < 0) || (start >= count))
1938 return;
1940 if (end >= 0)
1941 count = end + 1;
1943 float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
1945 for (int32 i = start; i < count; i++) {
1946 BListItem *item = ItemAt(i);
1947 item->SetTop(top);
1948 top += ceilf(item->Height());
1953 void
1954 BListView::_DoneTracking(BPoint where)
1956 fTrack->try_drag = false;
1957 fTrack->is_dragging = false;
1961 void
1962 BListView::_Track(BPoint where, uint32)
1964 if (fTrack->item_index >= 0 && fTrack->try_drag) {
1965 // initiate a drag if the mouse was moved far enough
1966 BPoint offset = where - fTrack->drag_start;
1967 float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
1968 if (dragDistance >= 5.0f) {
1969 fTrack->try_drag = false;
1970 fTrack->is_dragging = InitiateDrag(fTrack->drag_start,
1971 fTrack->item_index, fTrack->was_selected);
1975 if (!fTrack->is_dragging) {
1976 // do selection only if a drag was not initiated
1977 int32 index = IndexOf(where);
1978 BListItem* item = ItemAt(index);
1979 if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
1980 Select(index, fListType == B_MULTIPLE_SELECTION_LIST
1981 && (modifiers() & B_SHIFT_KEY) != 0);
1982 ScrollToSelection();