tcp: Fix 64 bit build with debugging features enabled.
[haiku.git] / src / kits / interface / ListView.cpp
blob515ac8b4eb96eb4612a3e1b6388defdab898aa92
1 /*
2 * Copyright 2001-2013 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 <stdio.h>
18 #include <Autolock.h>
19 #include <LayoutUtils.h>
20 #include <PropertyInfo.h>
21 #include <ScrollBar.h>
22 #include <ScrollView.h>
23 #include <Window.h>
25 #include <binary_compatibility/Interface.h>
28 struct track_data {
29 BPoint drag_start;
30 int32 item_index;
31 bool was_selected;
32 bool try_drag;
33 bigtime_t last_click_time;
37 const float kDoubleClickTresh = 6;
40 static property_info sProperties[] = {
41 { "Item", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
42 "Returns the number of BListItems currently in the list.", 0,
43 { B_INT32_TYPE }
46 { "Item", { B_EXECUTE_PROPERTY, 0 }, { B_INDEX_SPECIFIER,
47 B_REVERSE_INDEX_SPECIFIER, B_RANGE_SPECIFIER,
48 B_REVERSE_RANGE_SPECIFIER, 0 },
49 "Select and invoke the specified items, first removing any existing "
50 "selection."
53 { "Selection", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
54 "Returns int32 count of items in the selection.", 0, { B_INT32_TYPE }
57 { "Selection", { B_EXECUTE_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
58 "Invoke items in selection."
61 { "Selection", { B_GET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
62 "Returns int32 indices of all items in the selection.", 0,
63 { B_INT32_TYPE }
66 { "Selection", { B_SET_PROPERTY, 0 }, { B_INDEX_SPECIFIER,
67 B_REVERSE_INDEX_SPECIFIER, B_RANGE_SPECIFIER,
68 B_REVERSE_RANGE_SPECIFIER, 0 },
69 "Extends current selection or deselects specified items. Boolean field "
70 "\"data\" chooses selection or deselection.", 0, { B_BOOL_TYPE }
73 { "Selection", { B_SET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
74 "Select or deselect all items in the selection. Boolean field \"data\" "
75 "chooses selection or deselection.", 0, { B_BOOL_TYPE }
80 BListView::BListView(BRect frame, const char* name, list_view_type type,
81 uint32 resizingMode, uint32 flags)
83 BView(frame, name, resizingMode, flags)
85 _InitObject(type);
89 BListView::BListView(const char* name, list_view_type type, uint32 flags)
91 BView(name, flags)
93 _InitObject(type);
97 BListView::BListView(list_view_type type)
99 BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE)
101 _InitObject(type);
105 BListView::BListView(BMessage* archive)
107 BView(archive)
109 int32 listType;
110 archive->FindInt32("_lv_type", &listType);
111 _InitObject((list_view_type)listType);
113 int32 i = 0;
114 BMessage subData;
115 while (archive->FindMessage("_l_items", i++, &subData) == B_OK) {
116 BArchivable* object = instantiate_object(&subData);
117 if (object == NULL)
118 continue;
120 BListItem* item = dynamic_cast<BListItem*>(object);
121 if (item != NULL)
122 AddItem(item);
125 if (archive->HasMessage("_msg")) {
126 BMessage* invokationMessage = new BMessage;
128 archive->FindMessage("_msg", invokationMessage);
129 SetInvocationMessage(invokationMessage);
132 if (archive->HasMessage("_2nd_msg")) {
133 BMessage* selectionMessage = new BMessage;
135 archive->FindMessage("_2nd_msg", selectionMessage);
136 SetSelectionMessage(selectionMessage);
141 BListView::~BListView()
143 // NOTE: According to BeBook, BListView does not free the items itself.
144 delete fTrack;
145 SetSelectionMessage(NULL);
149 // #pragma mark -
152 BArchivable*
153 BListView::Instantiate(BMessage* archive)
155 if (validate_instantiation(archive, "BListView"))
156 return new BListView(archive);
158 return NULL;
162 status_t
163 BListView::Archive(BMessage* data, bool deep) const
165 status_t status = BView::Archive(data, deep);
166 if (status < B_OK)
167 return status;
169 status = data->AddInt32("_lv_type", fListType);
170 if (status == B_OK && deep) {
171 BListItem* item;
172 int32 i = 0;
174 while ((item = ItemAt(i++)) != NULL) {
175 BMessage subData;
176 status = item->Archive(&subData, true);
177 if (status >= B_OK)
178 status = data->AddMessage("_l_items", &subData);
180 if (status < B_OK)
181 break;
185 if (status >= B_OK && InvocationMessage() != NULL)
186 status = data->AddMessage("_msg", InvocationMessage());
188 if (status == B_OK && fSelectMessage != NULL)
189 status = data->AddMessage("_2nd_msg", fSelectMessage);
191 return status;
195 // #pragma mark -
198 void
199 BListView::Draw(BRect updateRect)
201 int32 count = CountItems();
202 if (count == 0)
203 return;
205 BRect itemFrame(0, 0, Bounds().right, -1);
206 for (int i = 0; i < count; i++) {
207 BListItem* item = ItemAt(i);
208 itemFrame.bottom = itemFrame.top + ceilf(item->Height()) - 1;
210 if (itemFrame.Intersects(updateRect))
211 DrawItem(item, itemFrame);
213 itemFrame.top = itemFrame.bottom + 1;
218 void
219 BListView::AttachedToWindow()
221 BView::AttachedToWindow();
222 _FontChanged();
224 if (!Messenger().IsValid())
225 SetTarget(Window(), NULL);
227 _FixupScrollBar();
231 void
232 BListView::DetachedFromWindow()
234 BView::DetachedFromWindow();
238 void
239 BListView::AllAttached()
241 BView::AllAttached();
245 void
246 BListView::AllDetached()
248 BView::AllDetached();
252 void
253 BListView::FrameResized(float newWidth, float newHeight)
255 _FixupScrollBar();
259 void
260 BListView::FrameMoved(BPoint newPosition)
262 BView::FrameMoved(newPosition);
266 void
267 BListView::TargetedByScrollView(BScrollView* view)
269 fScrollView = view;
270 // TODO: We could SetFlags(Flags() | B_FRAME_EVENTS) here, but that
271 // may mess up application code which manages this by some other means
272 // and doesn't want us to be messing with flags.
276 void
277 BListView::WindowActivated(bool active)
279 BView::WindowActivated(active);
283 // #pragma mark -
286 void
287 BListView::MessageReceived(BMessage* message)
289 switch (message->what) {
290 case B_COUNT_PROPERTIES:
291 case B_EXECUTE_PROPERTY:
292 case B_GET_PROPERTY:
293 case B_SET_PROPERTY:
295 BPropertyInfo propInfo(sProperties);
296 BMessage specifier;
297 const char *property;
299 if (message->GetCurrentSpecifier(NULL, &specifier) != B_OK
300 || specifier.FindString("property", &property) != B_OK)
301 return;
303 switch (propInfo.FindMatch(message, 0, &specifier, message->what,
304 property)) {
305 case B_ERROR:
306 BView::MessageReceived(message);
307 break;
309 case 0:
311 BMessage reply(B_REPLY);
312 reply.AddInt32("result", CountItems());
313 reply.AddInt32("error", B_OK);
315 message->SendReply(&reply);
316 break;
319 case 1:
320 break;
322 case 2:
324 int32 count = 0;
326 for (int32 i = 0; i < CountItems(); i++) {
327 if (ItemAt(i)->IsSelected())
328 count++;
331 BMessage reply(B_REPLY);
332 reply.AddInt32("result", count);
333 reply.AddInt32("error", B_OK);
335 message->SendReply(&reply);
336 break;
339 case 3:
340 break;
342 case 4:
344 BMessage reply (B_REPLY);
346 for (int32 i = 0; i < CountItems(); i++) {
347 if (ItemAt(i)->IsSelected())
348 reply.AddInt32("result", i);
351 reply.AddInt32("error", B_OK);
353 message->SendReply(&reply);
354 break;
357 case 5:
358 break;
360 case 6:
362 BMessage reply(B_REPLY);
364 bool select;
365 if (message->FindBool("data", &select) == B_OK && select)
366 Select(0, CountItems() - 1, false);
367 else
368 DeselectAll();
370 reply.AddInt32("error", B_OK);
372 message->SendReply(&reply);
373 break;
376 break;
379 case B_SELECT_ALL:
380 if (fListType == B_MULTIPLE_SELECTION_LIST)
381 Select(0, CountItems() - 1, false);
382 break;
384 default:
385 BView::MessageReceived(message);
390 void
391 BListView::KeyDown(const char* bytes, int32 numBytes)
393 bool extend = fListType == B_MULTIPLE_SELECTION_LIST
394 && (modifiers() & B_SHIFT_KEY) != 0;
396 if (fFirstSelected == -1
397 && (bytes[0] == B_UP_ARROW || bytes[0] == B_DOWN_ARROW)) {
398 // nothing is selected yet, select the first enabled item
399 int32 lastItem = CountItems() - 1;
400 for (int32 i = 0; i <= lastItem; i++) {
401 if (ItemAt(i)->IsEnabled()) {
402 Select(i);
403 break;
406 return;
409 switch (bytes[0]) {
410 case B_UP_ARROW:
412 if (fAnchorIndex > 0) {
413 if (!extend || fAnchorIndex <= fFirstSelected) {
414 for (int32 i = 1; fAnchorIndex - i >= 0; i++) {
415 if (ItemAt(fAnchorIndex - i)->IsEnabled()) {
416 // Select the previous enabled item
417 Select(fAnchorIndex - i, extend);
418 break;
421 } else {
422 Deselect(fAnchorIndex);
424 fAnchorIndex--;
425 while (fAnchorIndex > 0
426 && !ItemAt(fAnchorIndex)->IsEnabled());
430 ScrollToSelection();
431 break;
434 case B_DOWN_ARROW:
436 int32 lastItem = CountItems() - 1;
437 if (fAnchorIndex < lastItem) {
438 if (!extend || fAnchorIndex >= fLastSelected) {
439 for (int32 i = 1; fAnchorIndex + i <= lastItem; i++) {
440 if (ItemAt(fAnchorIndex + i)->IsEnabled()) {
441 // Select the next enabled item
442 Select(fAnchorIndex + i, extend);
443 break;
446 } else {
447 Deselect(fAnchorIndex);
449 fAnchorIndex++;
450 while (fAnchorIndex < lastItem
451 && !ItemAt(fAnchorIndex)->IsEnabled());
455 ScrollToSelection();
456 break;
459 case B_HOME:
460 if (extend) {
461 Select(0, fAnchorIndex, true);
462 fAnchorIndex = 0;
463 } else {
464 // select the first enabled item
465 int32 lastItem = CountItems() - 1;
466 for (int32 i = 0; i <= lastItem; i++) {
467 if (ItemAt(i)->IsEnabled()) {
468 Select(i, false);
469 break;
474 ScrollToSelection();
475 break;
477 case B_END:
478 if (extend) {
479 Select(fAnchorIndex, CountItems() - 1, true);
480 fAnchorIndex = CountItems() - 1;
481 } else {
482 // select the last enabled item
483 for (int32 i = CountItems() - 1; i >= 0; i--) {
484 if (ItemAt(i)->IsEnabled()) {
485 Select(i, false);
486 break;
491 ScrollToSelection();
492 break;
494 case B_PAGE_UP:
496 BPoint scrollOffset(LeftTop());
497 scrollOffset.y = max_c(0, scrollOffset.y - Bounds().Height());
498 ScrollTo(scrollOffset);
499 break;
502 case B_PAGE_DOWN:
504 BPoint scrollOffset(LeftTop());
505 if (BListItem* item = LastItem()) {
506 scrollOffset.y += Bounds().Height();
507 scrollOffset.y = min_c(item->Bottom() - Bounds().Height(),
508 scrollOffset.y);
510 ScrollTo(scrollOffset);
511 break;
514 case B_RETURN:
515 case B_SPACE:
516 Invoke();
517 break;
519 default:
520 BView::KeyDown(bytes, numBytes);
525 void
526 BListView::MouseDown(BPoint point)
528 if (!IsFocus()) {
529 MakeFocus();
530 Sync();
531 Window()->UpdateIfNeeded();
534 BMessage* message = Looper()->CurrentMessage();
535 int32 index = IndexOf(point);
537 // If the user double (or more) clicked within the current selection,
538 // we don't change the selection but invoke the selection.
539 // TODO: move this code someplace where it can be shared everywhere
540 // instead of every class having to reimplement it, once some sane
541 // API for it is decided.
542 BPoint delta = point - fTrack->drag_start;
543 bigtime_t sysTime;
544 Window()->CurrentMessage()->FindInt64("when", &sysTime);
545 bigtime_t timeDelta = sysTime - fTrack->last_click_time;
546 bigtime_t doubleClickSpeed;
547 get_click_speed(&doubleClickSpeed);
548 bool doubleClick = false;
550 if (timeDelta < doubleClickSpeed
551 && fabs(delta.x) < kDoubleClickTresh
552 && fabs(delta.y) < kDoubleClickTresh
553 && fTrack->item_index == index) {
554 doubleClick = true;
557 if (doubleClick && index >= fFirstSelected && index <= fLastSelected) {
558 fTrack->drag_start.Set(INT32_MAX, INT32_MAX);
559 Invoke();
560 return;
563 int32 modifiers;
564 message->FindInt32("modifiers", &modifiers);
566 if (!doubleClick) {
567 fTrack->drag_start = point;
568 fTrack->last_click_time = system_time();
569 fTrack->item_index = index;
570 fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false;
571 fTrack->try_drag = true;
574 if (index > -1) {
575 if (fListType == B_MULTIPLE_SELECTION_LIST) {
576 if (modifiers & B_SHIFT_KEY) {
577 // select entire block
578 // TODO: maybe review if we want it like in Tracker
579 // (anchor item)
580 if (index >= fFirstSelected && index < fLastSelected) {
581 // clicked inside of selected items block, deselect all
582 // but from the first selected item to the clicked item
583 DeselectExcept(fFirstSelected, index);
584 } else {
585 Select(min_c(index, fFirstSelected), max_c(index,
586 fLastSelected));
588 } else {
589 if (modifiers & B_COMMAND_KEY) {
590 // toggle selection state of clicked item (like in Tracker)
591 // toggle selection state of clicked item
592 if (ItemAt(index)->IsSelected())
593 Deselect(index);
594 else
595 Select(index, true);
596 } else
597 Select(index);
599 } else {
600 // toggle selection state of clicked item
601 if ((modifiers & B_COMMAND_KEY) && ItemAt(index)->IsSelected())
602 Deselect(index);
603 else
604 Select(index);
606 } else if ((modifiers & B_COMMAND_KEY) == 0)
607 DeselectAll();
611 void
612 BListView::MouseUp(BPoint where)
614 fTrack->try_drag = false;
618 void
619 BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
621 if (fTrack->item_index == -1 || !fTrack->try_drag) {
622 // mouse was not clicked above any item
623 // or no mouse button pressed
624 return;
627 // Initiate a drag if the mouse was moved far enough
628 BPoint offset = where - fTrack->drag_start;
629 float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
630 if (dragDistance >= 5.0f) {
631 fTrack->try_drag = false;
632 InitiateDrag(fTrack->drag_start, fTrack->item_index,
633 fTrack->was_selected);
638 bool
639 BListView::InitiateDrag(BPoint point, int32 index, bool wasSelected)
641 return false;
645 // #pragma mark -
648 void
649 BListView::ResizeToPreferred()
651 BView::ResizeToPreferred();
655 void
656 BListView::GetPreferredSize(float *_width, float *_height)
658 int32 count = CountItems();
660 if (count > 0) {
661 float maxWidth = 0.0;
662 for (int32 i = 0; i < count; i++) {
663 float itemWidth = ItemAt(i)->Width();
664 if (itemWidth > maxWidth)
665 maxWidth = itemWidth;
668 if (_width != NULL)
669 *_width = maxWidth;
670 if (_height != NULL)
671 *_height = ItemAt(count - 1)->Bottom();
672 } else
673 BView::GetPreferredSize(_width, _height);
677 BSize
678 BListView::MinSize()
680 // We need a stable min size: the BView implementation uses
681 // GetPreferredSize(), which by default just returns the current size.
682 return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
686 BSize
687 BListView::MaxSize()
689 return BView::MaxSize();
693 BSize
694 BListView::PreferredSize()
696 // We need a stable preferred size: the BView implementation uses
697 // GetPreferredSize(), which by default just returns the current size.
698 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
702 // #pragma mark -
705 void
706 BListView::MakeFocus(bool focused)
708 if (IsFocus() == focused)
709 return;
711 BView::MakeFocus(focused);
713 if (fScrollView)
714 fScrollView->SetBorderHighlighted(focused);
718 void
719 BListView::SetFont(const BFont* font, uint32 mask)
721 BView::SetFont(font, mask);
723 if (Window() != NULL && !Window()->InViewTransaction())
724 _FontChanged();
728 void
729 BListView::ScrollTo(BPoint point)
731 BView::ScrollTo(point);
735 // #pragma mark - List ops
738 bool
739 BListView::AddItem(BListItem* item, int32 index)
741 if (!fList.AddItem(item, index))
742 return false;
744 if (fFirstSelected != -1 && index <= fFirstSelected)
745 fFirstSelected++;
747 if (fLastSelected != -1 && index <= fLastSelected)
748 fLastSelected++;
750 if (Window()) {
751 BFont font;
752 GetFont(&font);
753 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
755 item->Update(this, &font);
756 _RecalcItemTops(index + 1);
758 _FixupScrollBar();
759 _InvalidateFrom(index);
762 return true;
766 bool
767 BListView::AddItem(BListItem* item)
769 if (!fList.AddItem(item))
770 return false;
772 // No need to adapt selection, as this item is the last in the list
774 if (Window()) {
775 BFont font;
776 GetFont(&font);
777 int32 index = CountItems() - 1;
778 item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
780 item->Update(this, &font);
782 _FixupScrollBar();
783 InvalidateItem(CountItems() - 1);
786 return true;
790 bool
791 BListView::AddList(BList* list, int32 index)
793 if (!fList.AddList(list, index))
794 return false;
796 int32 count = list->CountItems();
798 if (fFirstSelected != -1 && index < fFirstSelected)
799 fFirstSelected += count;
801 if (fLastSelected != -1 && index < fLastSelected)
802 fLastSelected += count;
804 if (Window()) {
805 BFont font;
806 GetFont(&font);
808 for (int32 i = index; i <= (index + count - 1); i++) {
809 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
810 ItemAt(i)->Update(this, &font);
813 _RecalcItemTops(index + count - 1);
815 _FixupScrollBar();
816 Invalidate(); // TODO
819 return true;
823 bool
824 BListView::AddList(BList* list)
826 return AddList(list, CountItems());
830 BListItem*
831 BListView::RemoveItem(int32 index)
833 BListItem* item = ItemAt(index);
834 if (item == NULL)
835 return NULL;
837 if (item->IsSelected())
838 Deselect(index);
840 if (!fList.RemoveItem(item))
841 return NULL;
843 if (fFirstSelected != -1 && index < fFirstSelected)
844 fFirstSelected--;
846 if (fLastSelected != -1 && index < fLastSelected)
847 fLastSelected--;
849 if (fAnchorIndex != -1 && index < fAnchorIndex)
850 fAnchorIndex--;
852 _RecalcItemTops(index);
854 _InvalidateFrom(index);
855 _FixupScrollBar();
857 return item;
861 bool
862 BListView::RemoveItem(BListItem* item)
864 return BListView::RemoveItem(IndexOf(item)) != NULL;
868 bool
869 BListView::RemoveItems(int32 index, int32 count)
871 if (index >= fList.CountItems())
872 index = -1;
874 if (index < 0)
875 return false;
877 if (fAnchorIndex != -1 && index < fAnchorIndex)
878 fAnchorIndex = index;
880 fList.RemoveItems(index, count);
881 if (index < fList.CountItems())
882 _RecalcItemTops(index);
884 Invalidate();
885 return true;
889 void
890 BListView::SetSelectionMessage(BMessage* message)
892 delete fSelectMessage;
893 fSelectMessage = message;
897 void
898 BListView::SetInvocationMessage(BMessage* message)
900 BInvoker::SetMessage(message);
904 BMessage*
905 BListView::InvocationMessage() const
907 return BInvoker::Message();
911 uint32
912 BListView::InvocationCommand() const
914 return BInvoker::Command();
918 BMessage*
919 BListView::SelectionMessage() const
921 return fSelectMessage;
925 uint32
926 BListView::SelectionCommand() const
928 if (fSelectMessage)
929 return fSelectMessage->what;
931 return 0;
935 void
936 BListView::SetListType(list_view_type type)
938 if (fListType == B_MULTIPLE_SELECTION_LIST
939 && type == B_SINGLE_SELECTION_LIST) {
940 Select(CurrentSelection(0));
943 fListType = type;
947 list_view_type
948 BListView::ListType() const
950 return fListType;
954 BListItem*
955 BListView::ItemAt(int32 index) const
957 return (BListItem*)fList.ItemAt(index);
961 int32
962 BListView::IndexOf(BListItem* item) const
964 if (Window()) {
965 if (item != NULL) {
966 int32 index = IndexOf(BPoint(0.0, item->Top()));
967 if (index >= 0 && fList.ItemAt(index) == item)
968 return index;
970 return -1;
973 return fList.IndexOf(item);
977 int32
978 BListView::IndexOf(BPoint point) const
980 int32 low = 0;
981 int32 high = fList.CountItems() - 1;
982 int32 mid = -1;
983 float frameTop = -1.0;
984 float frameBottom = 1.0;
986 // binary search the list
987 while (high >= low) {
988 mid = (low + high) / 2;
989 frameTop = ItemAt(mid)->Top();
990 frameBottom = ItemAt(mid)->Bottom();
991 if (point.y < frameTop)
992 high = mid - 1;
993 else if (point.y > frameBottom)
994 low = mid + 1;
995 else
996 return mid;
999 return -1;
1003 BListItem*
1004 BListView::FirstItem() const
1006 return (BListItem*)fList.FirstItem();
1010 BListItem*
1011 BListView::LastItem() const
1013 return (BListItem*)fList.LastItem();
1017 bool
1018 BListView::HasItem(BListItem *item) const
1020 return IndexOf(item) != -1;
1024 int32
1025 BListView::CountItems() const
1027 return fList.CountItems();
1031 void
1032 BListView::MakeEmpty()
1034 if (fList.IsEmpty())
1035 return;
1037 _DeselectAll(-1, -1);
1038 fList.MakeEmpty();
1040 if (Window()) {
1041 _FixupScrollBar();
1042 Invalidate();
1047 bool
1048 BListView::IsEmpty() const
1050 return fList.IsEmpty();
1054 void
1055 BListView::DoForEach(bool (*func)(BListItem*))
1057 fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
1061 void
1062 BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg)
1064 fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
1068 const BListItem**
1069 BListView::Items() const
1071 return (const BListItem**)fList.Items();
1075 void
1076 BListView::InvalidateItem(int32 index)
1078 Invalidate(ItemFrame(index));
1082 void
1083 BListView::ScrollToSelection()
1085 BRect itemFrame = ItemFrame(CurrentSelection(0));
1087 if (Bounds().Contains(itemFrame))
1088 return;
1090 float scrollPos = itemFrame.top < Bounds().top ?
1091 itemFrame.top : itemFrame.bottom - Bounds().Height();
1093 if (itemFrame.top - scrollPos < Bounds().top)
1094 scrollPos = itemFrame.top;
1096 ScrollTo(itemFrame.left, scrollPos);
1100 void
1101 BListView::Select(int32 index, bool extend)
1103 if (_Select(index, extend)) {
1104 SelectionChanged();
1105 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1110 void
1111 BListView::Select(int32 start, int32 finish, bool extend)
1113 if (_Select(start, finish, extend)) {
1114 SelectionChanged();
1115 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1120 bool
1121 BListView::IsItemSelected(int32 index) const
1123 BListItem* item = ItemAt(index);
1124 if (item != NULL)
1125 return item->IsSelected();
1127 return false;
1131 int32
1132 BListView::CurrentSelection(int32 index) const
1134 if (fFirstSelected == -1)
1135 return -1;
1137 if (index == 0)
1138 return fFirstSelected;
1140 for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1141 if (ItemAt(i)->IsSelected()) {
1142 if (index == 0)
1143 return i;
1145 index--;
1149 return -1;
1153 status_t
1154 BListView::Invoke(BMessage* message)
1156 // Note, this is more or less a copy of BControl::Invoke() and should
1157 // stay that way (ie. changes done there should be adopted here)
1159 bool notify = false;
1160 uint32 kind = InvokeKind(&notify);
1162 BMessage clone(kind);
1163 status_t err = B_BAD_VALUE;
1165 if (!message && !notify)
1166 message = Message();
1168 if (!message) {
1169 if (!IsWatched())
1170 return err;
1171 } else
1172 clone = *message;
1174 clone.AddInt64("when", (int64)system_time());
1175 clone.AddPointer("source", this);
1176 clone.AddMessenger("be:sender", BMessenger(this));
1178 if (fListType == B_SINGLE_SELECTION_LIST)
1179 clone.AddInt32("index", fFirstSelected);
1180 else {
1181 if (fFirstSelected >= 0) {
1182 for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
1183 if (ItemAt(i)->IsSelected())
1184 clone.AddInt32("index", i);
1189 if (message)
1190 err = BInvoker::Invoke(&clone);
1192 SendNotices(kind, &clone);
1194 return err;
1198 void
1199 BListView::DeselectAll()
1201 if (_DeselectAll(-1, -1)) {
1202 SelectionChanged();
1203 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1208 void
1209 BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
1211 if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
1212 return;
1214 if (_DeselectAll(exceptFrom, exceptTo)) {
1215 SelectionChanged();
1216 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1221 void
1222 BListView::Deselect(int32 index)
1224 if (_Deselect(index)) {
1225 SelectionChanged();
1226 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1231 void
1232 BListView::SelectionChanged()
1234 // Hook method to be implemented by subclasses
1238 void
1239 BListView::SortItems(int (*cmp)(const void *, const void *))
1241 if (_DeselectAll(-1, -1)) {
1242 SelectionChanged();
1243 InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
1246 fList.SortItems(cmp);
1247 _RecalcItemTops(0);
1248 Invalidate();
1252 bool
1253 BListView::SwapItems(int32 a, int32 b)
1255 MiscData data;
1257 data.swap.a = a;
1258 data.swap.b = b;
1260 return DoMiscellaneous(B_SWAP_OP, &data);
1264 bool
1265 BListView::MoveItem(int32 from, int32 to)
1267 MiscData data;
1269 data.move.from = from;
1270 data.move.to = to;
1272 return DoMiscellaneous(B_MOVE_OP, &data);
1276 bool
1277 BListView::ReplaceItem(int32 index, BListItem* item)
1279 MiscData data;
1281 data.replace.index = index;
1282 data.replace.item = item;
1284 return DoMiscellaneous(B_REPLACE_OP, &data);
1288 BRect
1289 BListView::ItemFrame(int32 index)
1291 BRect frame = Bounds();
1292 if (index < 0 || index >= CountItems()) {
1293 frame.top = 0;
1294 frame.bottom = -1;
1295 } else {
1296 BListItem* item = ItemAt(index);
1297 frame.top = item->Top();
1298 frame.bottom = item->Bottom();
1300 return frame;
1304 // #pragma mark -
1307 BHandler*
1308 BListView::ResolveSpecifier(BMessage* message, int32 index,
1309 BMessage* specifier, int32 what, const char* property)
1311 BPropertyInfo propInfo(sProperties);
1313 if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) {
1314 return BView::ResolveSpecifier(message, index, specifier, what,
1315 property);
1318 // TODO: msg->AddInt32("_match_code_", );
1320 return this;
1324 status_t
1325 BListView::GetSupportedSuites(BMessage* data)
1327 if (data == NULL)
1328 return B_BAD_VALUE;
1330 status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
1332 BPropertyInfo propertyInfo(sProperties);
1333 if (err == B_OK)
1334 err = data->AddFlat("messages", &propertyInfo);
1336 if (err == B_OK)
1337 return BView::GetSupportedSuites(data);
1338 return err;
1342 status_t
1343 BListView::Perform(perform_code code, void* _data)
1345 switch (code) {
1346 case PERFORM_CODE_MIN_SIZE:
1347 ((perform_data_min_size*)_data)->return_value
1348 = BListView::MinSize();
1349 return B_OK;
1350 case PERFORM_CODE_MAX_SIZE:
1351 ((perform_data_max_size*)_data)->return_value
1352 = BListView::MaxSize();
1353 return B_OK;
1354 case PERFORM_CODE_PREFERRED_SIZE:
1355 ((perform_data_preferred_size*)_data)->return_value
1356 = BListView::PreferredSize();
1357 return B_OK;
1358 case PERFORM_CODE_LAYOUT_ALIGNMENT:
1359 ((perform_data_layout_alignment*)_data)->return_value
1360 = BListView::LayoutAlignment();
1361 return B_OK;
1362 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1363 ((perform_data_has_height_for_width*)_data)->return_value
1364 = BListView::HasHeightForWidth();
1365 return B_OK;
1366 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1368 perform_data_get_height_for_width* data
1369 = (perform_data_get_height_for_width*)_data;
1370 BListView::GetHeightForWidth(data->width, &data->min, &data->max,
1371 &data->preferred);
1372 return B_OK;
1374 case PERFORM_CODE_SET_LAYOUT:
1376 perform_data_set_layout* data = (perform_data_set_layout*)_data;
1377 BListView::SetLayout(data->layout);
1378 return B_OK;
1380 case PERFORM_CODE_LAYOUT_INVALIDATED:
1382 perform_data_layout_invalidated* data
1383 = (perform_data_layout_invalidated*)_data;
1384 BListView::LayoutInvalidated(data->descendants);
1385 return B_OK;
1387 case PERFORM_CODE_DO_LAYOUT:
1389 BListView::DoLayout();
1390 return B_OK;
1394 return BView::Perform(code, _data);
1398 bool
1399 BListView::DoMiscellaneous(MiscCode code, MiscData* data)
1401 if (code > B_SWAP_OP)
1402 return false;
1404 switch (code) {
1405 case B_NO_OP:
1406 break;
1408 case B_REPLACE_OP:
1409 return _ReplaceItem(data->replace.index, data->replace.item);
1411 case B_MOVE_OP:
1412 return _MoveItem(data->move.from, data->move.to);
1414 case B_SWAP_OP:
1415 return _SwapItems(data->swap.a, data->swap.b);
1418 return false;
1422 // #pragma mark -
1425 void BListView::_ReservedListView2() {}
1426 void BListView::_ReservedListView3() {}
1427 void BListView::_ReservedListView4() {}
1430 BListView&
1431 BListView::operator=(const BListView& /*other*/)
1433 return *this;
1437 // #pragma mark -
1440 void
1441 BListView::_InitObject(list_view_type type)
1443 fListType = type;
1444 fFirstSelected = -1;
1445 fLastSelected = -1;
1446 fAnchorIndex = -1;
1447 fSelectMessage = NULL;
1448 fScrollView = NULL;
1449 fTrack = new track_data;
1450 fTrack->try_drag = false;
1451 fTrack->item_index = -1;
1453 SetViewColor(ui_color(B_LIST_BACKGROUND_COLOR));
1454 SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR));
1458 void
1459 BListView::_FixupScrollBar()
1461 BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
1462 if (!vertScroller)
1463 return;
1465 BRect bounds = Bounds();
1466 int32 count = CountItems();
1468 float itemHeight = 0.0;
1470 if (CountItems() > 0)
1471 itemHeight = ItemAt(CountItems() - 1)->Bottom();
1473 if (bounds.Height() > itemHeight) {
1474 // no scrolling
1475 vertScroller->SetRange(0.0, 0.0);
1476 vertScroller->SetValue(0.0);
1477 // also scrolls ListView to the top
1478 } else {
1479 vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
1480 vertScroller->SetProportion(bounds.Height () / itemHeight);
1481 // scroll up if there is empty room on bottom
1482 if (itemHeight < bounds.bottom)
1483 ScrollBy(0.0, bounds.bottom - itemHeight);
1486 if (count != 0)
1487 vertScroller->SetSteps(ceilf(FirstItem()->Height()), bounds.Height());
1491 void
1492 BListView::_InvalidateFrom(int32 index)
1494 // make sure index is behind last valid index
1495 int32 count = CountItems();
1496 if (index >= count)
1497 index = count;
1499 // take the item before the wanted one,
1500 // because that might already be removed
1501 index--;
1502 BRect dirty = Bounds();
1503 if (index >= 0)
1504 dirty.top = ItemFrame(index).bottom + 1;
1506 Invalidate(dirty);
1510 void
1511 BListView::_FontChanged()
1513 BFont font;
1514 GetFont(&font);
1515 for (int32 i = 0; i < CountItems(); i++) {
1516 ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
1517 ItemAt(i)->Update(this, &font);
1522 /*! Selects the item at the specified \a index, and returns \c true in
1523 case the selection was changed because of this method.
1524 If \a extend is \c false, all previously selected items are deselected.
1526 bool
1527 BListView::_Select(int32 index, bool extend)
1529 if (index < 0 || index >= CountItems())
1530 return false;
1532 // only lock the window when there is one
1533 BAutolock locker(Window());
1534 if (Window() != NULL && !locker.IsLocked())
1535 return false;
1537 bool changed = false;
1539 if (!extend && fFirstSelected != -1)
1540 changed = _DeselectAll(index, index);
1542 fAnchorIndex = index;
1544 BListItem* item = ItemAt(index);
1545 if (!item->IsEnabled() || item->IsSelected()) {
1546 // if the item is already selected, or can't be selected,
1547 // we're done here
1548 return changed;
1551 // keep track of first and last selected item
1552 if (fFirstSelected == -1) {
1553 // no previous selection
1554 fFirstSelected = index;
1555 fLastSelected = index;
1556 } else if (index < fFirstSelected) {
1557 fFirstSelected = index;
1558 } else if (index > fLastSelected) {
1559 fLastSelected = index;
1562 item->Select();
1563 if (Window() != NULL)
1564 InvalidateItem(index);
1566 return true;
1571 Selects the items between \a from and \a to, and returns \c true in
1572 case the selection was changed because of this method.
1573 If \a extend is \c false, all previously selected items are deselected.
1575 bool
1576 BListView::_Select(int32 from, int32 to, bool extend)
1578 if (to < from)
1579 return false;
1581 BAutolock locker(Window());
1582 if (Window() && !locker.IsLocked())
1583 return false;
1585 bool changed = false;
1587 if (fFirstSelected != -1 && !extend)
1588 changed = _DeselectAll(from, to);
1590 if (fFirstSelected == -1) {
1591 fFirstSelected = from;
1592 fLastSelected = to;
1593 } else {
1594 if (from < fFirstSelected)
1595 fFirstSelected = from;
1596 if (to > fLastSelected)
1597 fLastSelected = to;
1600 for (int32 i = from; i <= to; ++i) {
1601 BListItem* item = ItemAt(i);
1602 if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
1603 item->Select();
1604 if (Window() != NULL)
1605 InvalidateItem(i);
1606 changed = true;
1610 return changed;
1614 bool
1615 BListView::_Deselect(int32 index)
1617 if (index < 0 || index >= CountItems())
1618 return false;
1620 BWindow* window = Window();
1621 BAutolock locker(window);
1622 if (window != NULL && !locker.IsLocked())
1623 return false;
1625 BListItem* item = ItemAt(index);
1627 if (item != NULL && item->IsSelected()) {
1628 BRect frame(ItemFrame(index));
1629 BRect bounds(Bounds());
1631 item->Deselect();
1633 if (fFirstSelected == index && fLastSelected == index) {
1634 fFirstSelected = -1;
1635 fLastSelected = -1;
1636 } else {
1637 if (fFirstSelected == index)
1638 fFirstSelected = _CalcFirstSelected(index);
1640 if (fLastSelected == index)
1641 fLastSelected = _CalcLastSelected(index);
1644 if (window && bounds.Intersects(frame))
1645 DrawItem(ItemAt(index), frame, true);
1648 return true;
1652 bool
1653 BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
1655 if (fFirstSelected == -1)
1656 return false;
1658 BAutolock locker(Window());
1659 if (Window() && !locker.IsLocked())
1660 return false;
1662 bool changed = false;
1664 for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
1665 // don't deselect the items we shouldn't deselect
1666 if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
1667 continue;
1669 BListItem* item = ItemAt(index);
1670 if (item != NULL && item->IsSelected()) {
1671 item->Deselect();
1672 InvalidateItem(index);
1673 changed = true;
1677 if (!changed)
1678 return false;
1680 if (exceptFrom != -1) {
1681 fFirstSelected = _CalcFirstSelected(exceptFrom);
1682 fLastSelected = _CalcLastSelected(exceptTo);
1683 } else
1684 fFirstSelected = fLastSelected = -1;
1686 return true;
1690 int32
1691 BListView::_CalcFirstSelected(int32 after)
1693 if (after >= CountItems())
1694 return -1;
1696 int32 count = CountItems();
1697 for (int32 i = after; i < count; i++) {
1698 if (ItemAt(i)->IsSelected())
1699 return i;
1702 return -1;
1706 int32
1707 BListView::_CalcLastSelected(int32 before)
1709 if (before < 0)
1710 return -1;
1712 before = min_c(CountItems() - 1, before);
1714 for (int32 i = before; i >= 0; i--) {
1715 if (ItemAt(i)->IsSelected())
1716 return i;
1719 return -1;
1723 void
1724 BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
1726 if (!item->IsEnabled()) {
1727 rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
1728 rgb_color disabledColor;
1729 if (textColor.red + textColor.green + textColor.blue > 128 * 3)
1730 disabledColor = tint_color(textColor, B_DARKEN_2_TINT);
1731 else
1732 disabledColor = tint_color(textColor, B_LIGHTEN_2_TINT);
1734 SetHighColor(disabledColor);
1735 } else if (item->IsSelected())
1736 SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
1737 else
1738 SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
1740 item->DrawItem(this, itemRect, complete);
1744 bool
1745 BListView::_SwapItems(int32 a, int32 b)
1747 // remember frames of items before anything happens,
1748 // the tricky situation is when the two items have
1749 // a different height
1750 BRect aFrame = ItemFrame(a);
1751 BRect bFrame = ItemFrame(b);
1753 if (!fList.SwapItems(a, b))
1754 return false;
1756 if (a == b) {
1757 // nothing to do, but success nevertheless
1758 return true;
1761 // track anchor item
1762 if (fAnchorIndex == a)
1763 fAnchorIndex = b;
1764 else if (fAnchorIndex == b)
1765 fAnchorIndex = a;
1767 // track selection
1768 // NOTE: this is only important if the selection status
1769 // of both items is not the same
1770 int32 first = min_c(a, b);
1771 int32 last = max_c(a, b);
1772 if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
1773 if (first < fFirstSelected || last > fLastSelected) {
1774 _RescanSelection(min_c(first, fFirstSelected),
1775 max_c(last, fLastSelected));
1777 // though the actually selected items stayed the
1778 // same, the selection has still changed
1779 SelectionChanged();
1782 ItemAt(a)->SetTop(aFrame.top);
1783 ItemAt(b)->SetTop(bFrame.top);
1785 // take care of invalidation
1786 if (Window()) {
1787 // NOTE: window looper is assumed to be locked!
1788 if (aFrame.Height() != bFrame.Height()) {
1789 _RecalcItemTops(first, last);
1790 // items in between shifted visually
1791 Invalidate(aFrame | bFrame);
1792 } else {
1793 Invalidate(aFrame);
1794 Invalidate(bFrame);
1798 return true;
1802 bool
1803 BListView::_MoveItem(int32 from, int32 to)
1805 // remember item frames before doing anything
1806 BRect frameFrom = ItemFrame(from);
1807 BRect frameTo = ItemFrame(to);
1809 if (!fList.MoveItem(from, to))
1810 return false;
1812 // track anchor item
1813 if (fAnchorIndex == from)
1814 fAnchorIndex = to;
1816 // track selection
1817 if (ItemAt(to)->IsSelected()) {
1818 _RescanSelection(from, to);
1819 // though the actually selected items stayed the
1820 // same, the selection has still changed
1821 SelectionChanged();
1824 _RecalcItemTops((to > from) ? from : to);
1826 // take care of invalidation
1827 if (Window()) {
1828 // NOTE: window looper is assumed to be locked!
1829 Invalidate(frameFrom | frameTo);
1832 return true;
1836 bool
1837 BListView::_ReplaceItem(int32 index, BListItem* item)
1839 if (item == NULL)
1840 return false;
1842 BListItem* old = ItemAt(index);
1843 if (!old)
1844 return false;
1846 BRect frame = ItemFrame(index);
1848 bool selectionChanged = old->IsSelected() != item->IsSelected();
1850 // replace item
1851 if (!fList.ReplaceItem(index, item))
1852 return false;
1854 // tack selection
1855 if (selectionChanged) {
1856 int32 start = min_c(fFirstSelected, index);
1857 int32 end = max_c(fLastSelected, index);
1858 _RescanSelection(start, end);
1859 SelectionChanged();
1861 _RecalcItemTops(index);
1863 bool itemHeightChanged = frame != ItemFrame(index);
1865 // take care of invalidation
1866 if (Window()) {
1867 // NOTE: window looper is assumed to be locked!
1868 if (itemHeightChanged)
1869 _InvalidateFrom(index);
1870 else
1871 Invalidate(frame);
1874 if (itemHeightChanged)
1875 _FixupScrollBar();
1877 return true;
1881 void
1882 BListView::_RescanSelection(int32 from, int32 to)
1884 if (from > to) {
1885 int32 tmp = from;
1886 from = to;
1887 to = tmp;
1890 from = max_c(0, from);
1891 to = min_c(to, CountItems() - 1);
1893 if (fAnchorIndex != -1) {
1894 if (fAnchorIndex == from)
1895 fAnchorIndex = to;
1896 else if (fAnchorIndex == to)
1897 fAnchorIndex = from;
1900 for (int32 i = from; i <= to; i++) {
1901 if (ItemAt(i)->IsSelected()) {
1902 fFirstSelected = i;
1903 break;
1907 if (fFirstSelected > from)
1908 from = fFirstSelected;
1910 fLastSelected = fFirstSelected;
1911 for (int32 i = from; i <= to; i++) {
1912 if (ItemAt(i)->IsSelected())
1913 fLastSelected = i;
1918 void
1919 BListView::_RecalcItemTops(int32 start, int32 end)
1921 int32 count = CountItems();
1922 if ((start < 0) || (start >= count))
1923 return;
1925 if (end >= 0)
1926 count = end + 1;
1928 float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
1930 for (int32 i = start; i < count; i++) {
1931 BListItem *item = ItemAt(i);
1932 item->SetTop(top);
1933 top += ceilf(item->Height());