2 * Copyright 2001-2015 Haiku, Inc. All rights resrerved.
3 * Distributed under the terms of the MIT license.
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
21 #include <LayoutUtils.h>
22 #include <PropertyInfo.h>
23 #include <ScrollBar.h>
24 #include <ScrollView.h>
28 #include <binary_compatibility/Interface.h>
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,
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 "
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,
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
}
86 BListView::BListView(BRect frame
, const char* name
, list_view_type type
,
87 uint32 resizingMode
, uint32 flags
)
89 BView(frame
, name
, resizingMode
, flags
)
95 BListView::BListView(const char* name
, list_view_type type
, uint32 flags
)
103 BListView::BListView(list_view_type type
)
105 BView(NULL
, B_WILL_DRAW
| B_FRAME_EVENTS
| B_NAVIGABLE
)
111 BListView::BListView(BMessage
* archive
)
116 archive
->FindInt32("_lv_type", &listType
);
117 _InitObject((list_view_type
)listType
);
121 while (archive
->FindMessage("_l_items", i
++, &subData
) == B_OK
) {
122 BArchivable
* object
= instantiate_object(&subData
);
126 BListItem
* item
= dynamic_cast<BListItem
*>(object
);
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.
151 SetSelectionMessage(NULL
);
159 BListView::Instantiate(BMessage
* archive
)
161 if (validate_instantiation(archive
, "BListView"))
162 return new BListView(archive
);
169 BListView::Archive(BMessage
* data
, bool deep
) const
171 status_t status
= BView::Archive(data
, deep
);
175 status
= data
->AddInt32("_lv_type", fListType
);
176 if (status
== B_OK
&& deep
) {
180 while ((item
= ItemAt(i
++)) != NULL
) {
182 status
= item
->Archive(&subData
, true);
184 status
= data
->AddMessage("_l_items", &subData
);
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
);
205 BListView::Draw(BRect updateRect
)
207 int32 count
= CountItems();
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;
225 BListView::AttachedToWindow()
227 BView::AttachedToWindow();
230 if (!Messenger().IsValid())
231 SetTarget(Window(), NULL
);
238 BListView::DetachedFromWindow()
240 BView::DetachedFromWindow();
245 BListView::AllAttached()
247 BView::AllAttached();
252 BListView::AllDetached()
254 BView::AllDetached();
259 BListView::FrameResized(float newWidth
, float newHeight
)
263 // notify items of new width.
269 BListView::FrameMoved(BPoint newPosition
)
271 BView::FrameMoved(newPosition
);
276 BListView::TargetedByScrollView(BScrollView
* 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.
286 BListView::WindowActivated(bool active
)
288 BView::WindowActivated(active
);
296 BListView::MessageReceived(BMessage
* message
)
298 switch (message
->what
) {
299 case B_MOUSE_WHEEL_CHANGED
:
300 if (!fTrack
->is_dragging
)
301 BView::MessageReceived(message
);
304 case B_COUNT_PROPERTIES
:
305 case B_EXECUTE_PROPERTY
:
309 BPropertyInfo
propInfo(sProperties
);
311 const char* property
;
313 if (message
->GetCurrentSpecifier(NULL
, &specifier
) != B_OK
314 || specifier
.FindString("property", &property
) != B_OK
) {
318 switch (propInfo
.FindMatch(message
, 0, &specifier
, message
->what
,
321 BView::MessageReceived(message
);
326 BMessage
reply(B_REPLY
);
327 reply
.AddInt32("result", CountItems());
328 reply
.AddInt32("error", B_OK
);
330 message
->SendReply(&reply
);
341 for (int32 i
= 0; i
< CountItems(); i
++) {
342 if (ItemAt(i
)->IsSelected())
346 BMessage
reply(B_REPLY
);
347 reply
.AddInt32("result", count
);
348 reply
.AddInt32("error", B_OK
);
350 message
->SendReply(&reply
);
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
);
377 BMessage
reply(B_REPLY
);
380 if (message
->FindBool("data", &select
) == B_OK
&& select
)
381 Select(0, CountItems() - 1, false);
385 reply
.AddInt32("error", B_OK
);
387 message
->SendReply(&reply
);
395 if (fListType
== B_MULTIPLE_SELECTION_LIST
)
396 Select(0, CountItems() - 1, false);
400 BView::MessageReceived(message
);
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()) {
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
);
437 Deselect(fAnchorIndex
);
440 while (fAnchorIndex
> 0
441 && !ItemAt(fAnchorIndex
)->IsEnabled());
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
);
462 Deselect(fAnchorIndex
);
465 while (fAnchorIndex
< lastItem
466 && !ItemAt(fAnchorIndex
)->IsEnabled());
476 Select(0, fAnchorIndex
, true);
479 // select the first enabled item
480 int32 lastItem
= CountItems() - 1;
481 for (int32 i
= 0; i
<= lastItem
; i
++) {
482 if (ItemAt(i
)->IsEnabled()) {
494 Select(fAnchorIndex
, CountItems() - 1, true);
495 fAnchorIndex
= CountItems() - 1;
497 // select the last enabled item
498 for (int32 i
= CountItems() - 1; i
>= 0; i
--) {
499 if (ItemAt(i
)->IsEnabled()) {
511 BPoint
scrollOffset(LeftTop());
512 scrollOffset
.y
= std::max(0.0f
, scrollOffset
.y
- Bounds().Height());
513 ScrollTo(scrollOffset
);
519 BPoint
scrollOffset(LeftTop());
520 if (BListItem
* item
= LastItem()) {
521 scrollOffset
.y
+= Bounds().Height();
522 scrollOffset
.y
= std::min(item
->Bottom() - Bounds().Height(),
525 ScrollTo(scrollOffset
);
535 BView::KeyDown(bytes
, numBytes
);
541 BListView::MouseDown(BPoint where
)
546 Window()->UpdateIfNeeded();
549 BMessage
* message
= Looper()->CurrentMessage();
550 int32 index
= IndexOf(where
);
554 message
->FindInt32("buttons", &buttons
);
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
;
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
) {
580 if (doubleClick
&& index
>= fFirstSelected
&& index
<= fLastSelected
) {
581 fTrack
->drag_start
.Set(INT32_MAX
, INT32_MAX
);
583 return BView::MouseDown(where
);
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
);
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
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
);
608 Select(std::min(index
, fFirstSelected
), std::max(index
,
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())
623 // toggle selection state of clicked item
624 if ((modifiers
& B_COMMAND_KEY
) != 0 && ItemAt(index
)->IsSelected())
629 } else if ((modifiers
& B_COMMAND_KEY
) == 0)
632 BView::MouseDown(where
);
637 BListView::MouseUp(BPoint where
)
639 BView::MouseUp(where
);
644 BListView::MouseMoved(BPoint where
, uint32 code
, const BMessage
* dragMessage
)
646 BView::MouseMoved(where
, code
, dragMessage
);
651 BListView::InitiateDrag(BPoint where
, int32 index
, bool wasSelected
)
661 BListView::ResizeToPreferred()
663 BView::ResizeToPreferred();
668 BListView::GetPreferredSize(float *_width
, float *_height
)
670 int32 count
= CountItems();
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
;
683 *_height
= ItemAt(count
- 1)->Bottom();
685 BView::GetPreferredSize(_width
, _height
);
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));
701 return BView::MaxSize();
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));
718 BListView::MakeFocus(bool focused
)
720 if (IsFocus() == focused
)
723 BView::MakeFocus(focused
);
726 fScrollView
->SetBorderHighlighted(focused
);
731 BListView::SetFont(const BFont
* font
, uint32 mask
)
733 BView::SetFont(font
, mask
);
735 if (Window() != NULL
&& !Window()->InViewTransaction())
741 BListView::ScrollTo(BPoint point
)
743 BView::ScrollTo(point
);
747 // #pragma mark - List ops
751 BListView::AddItem(BListItem
* item
, int32 index
)
753 if (!fList
.AddItem(item
, index
))
756 if (fFirstSelected
!= -1 && index
<= fFirstSelected
)
759 if (fLastSelected
!= -1 && index
<= fLastSelected
)
765 item
->SetTop((index
> 0) ? ItemAt(index
- 1)->Bottom() + 1.0 : 0.0);
767 item
->Update(this, &font
);
768 _RecalcItemTops(index
+ 1);
771 _InvalidateFrom(index
);
779 BListView::AddItem(BListItem
* item
)
781 if (!fList
.AddItem(item
))
784 // No need to adapt selection, as this item is the last in the list
789 int32 index
= CountItems() - 1;
790 item
->SetTop((index
> 0) ? ItemAt(index
- 1)->Bottom() + 1.0 : 0.0);
792 item
->Update(this, &font
);
795 InvalidateItem(CountItems() - 1);
803 BListView::AddList(BList
* list
, int32 index
)
805 if (!fList
.AddList(list
, index
))
808 int32 count
= list
->CountItems();
810 if (fFirstSelected
!= -1 && index
< fFirstSelected
)
811 fFirstSelected
+= count
;
813 if (fLastSelected
!= -1 && index
< fLastSelected
)
814 fLastSelected
+= count
;
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);
828 Invalidate(); // TODO
836 BListView::AddList(BList
* list
)
838 return AddList(list
, CountItems());
843 BListView::RemoveItem(int32 index
)
845 BListItem
* item
= ItemAt(index
);
849 if (item
->IsSelected())
852 if (!fList
.RemoveItem(item
))
855 if (fFirstSelected
!= -1 && index
< fFirstSelected
)
858 if (fLastSelected
!= -1 && index
< fLastSelected
)
861 if (fAnchorIndex
!= -1 && index
< fAnchorIndex
)
864 _RecalcItemTops(index
);
866 _InvalidateFrom(index
);
874 BListView::RemoveItem(BListItem
* item
)
876 return BListView::RemoveItem(IndexOf(item
)) != NULL
;
881 BListView::RemoveItems(int32 index
, int32 count
)
883 if (index
>= fList
.CountItems())
889 if (fAnchorIndex
!= -1 && index
< fAnchorIndex
)
890 fAnchorIndex
= index
;
892 fList
.RemoveItems(index
, count
);
893 if (index
< fList
.CountItems())
894 _RecalcItemTops(index
);
902 BListView::SetSelectionMessage(BMessage
* message
)
904 delete fSelectMessage
;
905 fSelectMessage
= message
;
910 BListView::SetInvocationMessage(BMessage
* message
)
912 BInvoker::SetMessage(message
);
917 BListView::InvocationMessage() const
919 return BInvoker::Message();
924 BListView::InvocationCommand() const
926 return BInvoker::Command();
931 BListView::SelectionMessage() const
933 return fSelectMessage
;
938 BListView::SelectionCommand() const
941 return fSelectMessage
->what
;
948 BListView::SetListType(list_view_type type
)
950 if (fListType
== B_MULTIPLE_SELECTION_LIST
951 && type
== B_SINGLE_SELECTION_LIST
) {
952 Select(CurrentSelection(0));
960 BListView::ListType() const
967 BListView::ItemAt(int32 index
) const
969 return (BListItem
*)fList
.ItemAt(index
);
974 BListView::IndexOf(BListItem
* item
) const
978 int32 index
= IndexOf(BPoint(0.0, item
->Top()));
979 if (index
>= 0 && fList
.ItemAt(index
) == item
)
985 return fList
.IndexOf(item
);
990 BListView::IndexOf(BPoint point
) const
993 int32 high
= fList
.CountItems() - 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
)
1005 else if (point
.y
> frameBottom
)
1016 BListView::FirstItem() const
1018 return (BListItem
*)fList
.FirstItem();
1023 BListView::LastItem() const
1025 return (BListItem
*)fList
.LastItem();
1030 BListView::HasItem(BListItem
*item
) const
1032 return IndexOf(item
) != -1;
1037 BListView::CountItems() const
1039 return fList
.CountItems();
1044 BListView::MakeEmpty()
1046 if (fList
.IsEmpty())
1049 _DeselectAll(-1, -1);
1060 BListView::IsEmpty() const
1062 return fList
.IsEmpty();
1067 BListView::DoForEach(bool (*func
)(BListItem
*))
1069 fList
.DoForEach(reinterpret_cast<bool (*)(void*)>(func
));
1074 BListView::DoForEach(bool (*func
)(BListItem
*, void*), void* arg
)
1076 fList
.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func
), arg
);
1081 BListView::Items() const
1083 return (const BListItem
**)fList
.Items();
1088 BListView::InvalidateItem(int32 index
)
1090 Invalidate(ItemFrame(index
));
1095 BListView::ScrollToSelection()
1097 BRect itemFrame
= ItemFrame(CurrentSelection(0));
1099 if (Bounds().Contains(itemFrame
))
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
);
1113 BListView::Select(int32 index
, bool extend
)
1115 if (_Select(index
, extend
)) {
1117 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1123 BListView::Select(int32 start
, int32 finish
, bool extend
)
1125 if (_Select(start
, finish
, extend
)) {
1127 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1133 BListView::IsItemSelected(int32 index
) const
1135 BListItem
* item
= ItemAt(index
);
1137 return item
->IsSelected();
1144 BListView::CurrentSelection(int32 index
) const
1146 if (fFirstSelected
== -1)
1150 return fFirstSelected
;
1152 for (int32 i
= fFirstSelected
; i
<= fLastSelected
; i
++) {
1153 if (ItemAt(i
)->IsSelected()) {
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(¬ify
);
1174 BMessage
clone(kind
);
1175 status_t err
= B_BAD_VALUE
;
1177 if (!message
&& !notify
)
1178 message
= 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
);
1193 if (fFirstSelected
>= 0) {
1194 for (int32 i
= fFirstSelected
; i
<= fLastSelected
; i
++) {
1195 if (ItemAt(i
)->IsSelected())
1196 clone
.AddInt32("index", i
);
1202 err
= BInvoker::Invoke(&clone
);
1204 SendNotices(kind
, &clone
);
1211 BListView::DeselectAll()
1213 if (_DeselectAll(-1, -1)) {
1215 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1221 BListView::DeselectExcept(int32 exceptFrom
, int32 exceptTo
)
1223 if (exceptFrom
> exceptTo
|| exceptFrom
< 0 || exceptTo
< 0)
1226 if (_DeselectAll(exceptFrom
, exceptTo
)) {
1228 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1234 BListView::Deselect(int32 index
)
1236 if (_Deselect(index
)) {
1238 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1244 BListView::SelectionChanged()
1246 // Hook method to be implemented by subclasses
1251 BListView::SortItems(int (*cmp
)(const void *, const void *))
1253 if (_DeselectAll(-1, -1)) {
1255 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1258 fList
.SortItems(cmp
);
1265 BListView::SwapItems(int32 a
, int32 b
)
1272 return DoMiscellaneous(B_SWAP_OP
, &data
);
1277 BListView::MoveItem(int32 from
, int32 to
)
1281 data
.move
.from
= from
;
1284 return DoMiscellaneous(B_MOVE_OP
, &data
);
1289 BListView::ReplaceItem(int32 index
, BListItem
* item
)
1293 data
.replace
.index
= index
;
1294 data
.replace
.item
= item
;
1296 return DoMiscellaneous(B_REPLACE_OP
, &data
);
1301 BListView::ItemFrame(int32 index
)
1303 BRect frame
= Bounds();
1304 if (index
< 0 || index
>= CountItems()) {
1308 BListItem
* item
= ItemAt(index
);
1309 frame
.top
= item
->Top();
1310 frame
.bottom
= item
->Bottom();
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
,
1330 // TODO: msg->AddInt32("_match_code_", );
1337 BListView::GetSupportedSuites(BMessage
* data
)
1342 status_t err
= data
->AddString("suites", "suite/vnd.Be-list-view");
1344 BPropertyInfo
propertyInfo(sProperties
);
1346 err
= data
->AddFlat("messages", &propertyInfo
);
1349 return BView::GetSupportedSuites(data
);
1355 BListView::Perform(perform_code code
, void* _data
)
1358 case PERFORM_CODE_MIN_SIZE
:
1359 ((perform_data_min_size
*)_data
)->return_value
1360 = BListView::MinSize();
1362 case PERFORM_CODE_MAX_SIZE
:
1363 ((perform_data_max_size
*)_data
)->return_value
1364 = BListView::MaxSize();
1366 case PERFORM_CODE_PREFERRED_SIZE
:
1367 ((perform_data_preferred_size
*)_data
)->return_value
1368 = BListView::PreferredSize();
1370 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
1371 ((perform_data_layout_alignment
*)_data
)->return_value
1372 = BListView::LayoutAlignment();
1374 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
1375 ((perform_data_has_height_for_width
*)_data
)->return_value
1376 = BListView::HasHeightForWidth();
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
,
1386 case PERFORM_CODE_SET_LAYOUT
:
1388 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
1389 BListView::SetLayout(data
->layout
);
1392 case PERFORM_CODE_LAYOUT_INVALIDATED
:
1394 perform_data_layout_invalidated
* data
1395 = (perform_data_layout_invalidated
*)_data
;
1396 BListView::LayoutInvalidated(data
->descendants
);
1399 case PERFORM_CODE_DO_LAYOUT
:
1401 BListView::DoLayout();
1406 return BView::Perform(code
, _data
);
1411 BListView::DoMiscellaneous(MiscCode code
, MiscData
* data
)
1413 if (code
> B_SWAP_OP
)
1421 return _ReplaceItem(data
->replace
.index
, data
->replace
.item
);
1424 return _MoveItem(data
->move
.from
, data
->move
.to
);
1427 return _SwapItems(data
->swap
.a
, data
->swap
.b
);
1437 void BListView::_ReservedListView2() {}
1438 void BListView::_ReservedListView3() {}
1439 void BListView::_ReservedListView4() {}
1443 BListView::operator=(const BListView
& /*other*/)
1453 BListView::_InitObject(list_view_type type
)
1456 fFirstSelected
= -1;
1459 fSelectMessage
= 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
);
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
) {
1491 vertScroller
->SetRange(0.0, 0.0);
1492 vertScroller
->SetValue(0.0);
1493 // also scrolls ListView to the top
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
);
1503 vertScroller
->SetSteps(
1504 ceilf(FirstItem()->Height()), bounds
.Height());
1507 BScrollBar
* horizontalScroller
= ScrollBar(B_HORIZONTAL
);
1508 if (horizontalScroller
!= NULL
) {
1510 GetPreferredSize(&w
, NULL
);
1511 BRect scrollBarSize
= horizontalScroller
->Bounds();
1513 if (w
<= scrollBarSize
.Width()) {
1515 horizontalScroller
->SetRange(0.0, 0.0);
1516 horizontalScroller
->SetValue(0.0);
1518 horizontalScroller
->SetRange(0, w
- scrollBarSize
.Width());
1519 horizontalScroller
->SetProportion(scrollBarSize
.Width() / w
);
1521 printf("Range: %f - %f\n", w
, scrollBarSize
.Width());
1527 BListView::_InvalidateFrom(int32 index
)
1529 // make sure index is behind last valid index
1530 int32 count
= CountItems();
1534 // take the item before the wanted one,
1535 // because that might already be removed
1537 BRect dirty
= Bounds();
1539 dirty
.top
= ItemFrame(index
).bottom
+ 1;
1546 BListView::_UpdateItems()
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.
1562 BListView::_Select(int32 index
, bool extend
)
1564 if (index
< 0 || index
>= CountItems())
1567 // only lock the window when there is one
1568 BAutolock
locker(Window());
1569 if (Window() != NULL
&& !locker
.IsLocked())
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,
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
;
1598 if (Window() != NULL
)
1599 InvalidateItem(index
);
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.
1611 BListView::_Select(int32 from
, int32 to
, bool extend
)
1616 BAutolock
locker(Window());
1617 if (Window() && !locker
.IsLocked())
1620 bool changed
= false;
1622 if (fFirstSelected
!= -1 && !extend
)
1623 changed
= _DeselectAll(from
, to
);
1625 if (fFirstSelected
== -1) {
1626 fFirstSelected
= from
;
1629 if (from
< fFirstSelected
)
1630 fFirstSelected
= from
;
1631 if (to
> fLastSelected
)
1635 for (int32 i
= from
; i
<= to
; ++i
) {
1636 BListItem
* item
= ItemAt(i
);
1637 if (item
!= NULL
&& !item
->IsSelected() && item
->IsEnabled()) {
1639 if (Window() != NULL
)
1650 BListView::_Deselect(int32 index
)
1652 if (index
< 0 || index
>= CountItems())
1655 BWindow
* window
= Window();
1656 BAutolock
locker(window
);
1657 if (window
!= NULL
&& !locker
.IsLocked())
1660 BListItem
* item
= ItemAt(index
);
1662 if (item
!= NULL
&& item
->IsSelected()) {
1663 BRect
frame(ItemFrame(index
));
1664 BRect
bounds(Bounds());
1668 if (fFirstSelected
== index
&& fLastSelected
== index
) {
1669 fFirstSelected
= -1;
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);
1688 BListView::_DeselectAll(int32 exceptFrom
, int32 exceptTo
)
1690 if (fFirstSelected
== -1)
1693 BAutolock
locker(Window());
1694 if (Window() && !locker
.IsLocked())
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
)
1704 BListItem
* item
= ItemAt(index
);
1705 if (item
!= NULL
&& item
->IsSelected()) {
1707 InvalidateItem(index
);
1715 if (exceptFrom
!= -1) {
1716 fFirstSelected
= _CalcFirstSelected(exceptFrom
);
1717 fLastSelected
= _CalcLastSelected(exceptTo
);
1719 fFirstSelected
= fLastSelected
= -1;
1726 BListView::_CalcFirstSelected(int32 after
)
1728 if (after
>= CountItems())
1731 int32 count
= CountItems();
1732 for (int32 i
= after
; i
< count
; i
++) {
1733 if (ItemAt(i
)->IsSelected())
1742 BListView::_CalcLastSelected(int32 before
)
1747 before
= std::min(CountItems() - 1, before
);
1749 for (int32 i
= before
; i
>= 0; i
--) {
1750 if (ItemAt(i
)->IsSelected())
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
);
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
));
1773 SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR
));
1775 item
->DrawItem(this, itemRect
, complete
);
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
))
1792 // nothing to do, but success nevertheless
1796 // track anchor item
1797 if (fAnchorIndex
== a
)
1799 else if (fAnchorIndex
== b
)
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
1817 ItemAt(a
)->SetTop(aFrame
.top
);
1818 ItemAt(b
)->SetTop(bFrame
.top
);
1820 // take care of invalidation
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
);
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
))
1847 // track anchor item
1848 if (fAnchorIndex
== from
)
1852 if (ItemAt(to
)->IsSelected()) {
1853 _RescanSelection(from
, to
);
1854 // though the actually selected items stayed the
1855 // same, the selection has still changed
1859 _RecalcItemTops((to
> from
) ? from
: to
);
1861 // take care of invalidation
1863 // NOTE: window looper is assumed to be locked!
1864 Invalidate(frameFrom
| frameTo
);
1872 BListView::_ReplaceItem(int32 index
, BListItem
* item
)
1877 BListItem
* old
= ItemAt(index
);
1881 BRect frame
= ItemFrame(index
);
1883 bool selectionChanged
= old
->IsSelected() != item
->IsSelected();
1886 if (!fList
.ReplaceItem(index
, item
))
1890 if (selectionChanged
) {
1891 int32 start
= std::min(fFirstSelected
, index
);
1892 int32 end
= std::max(fLastSelected
, index
);
1893 _RescanSelection(start
, end
);
1896 _RecalcItemTops(index
);
1898 bool itemHeightChanged
= frame
!= ItemFrame(index
);
1900 // take care of invalidation
1902 // NOTE: window looper is assumed to be locked!
1903 if (itemHeightChanged
)
1904 _InvalidateFrom(index
);
1909 if (itemHeightChanged
)
1917 BListView::_RescanSelection(int32 from
, int32 to
)
1925 from
= std::max((int32
)0, from
);
1926 to
= std::min(to
, CountItems() - 1);
1928 if (fAnchorIndex
!= -1) {
1929 if (fAnchorIndex
== from
)
1931 else if (fAnchorIndex
== to
)
1932 fAnchorIndex
= from
;
1935 for (int32 i
= from
; i
<= to
; i
++) {
1936 if (ItemAt(i
)->IsSelected()) {
1942 if (fFirstSelected
> from
)
1943 from
= fFirstSelected
;
1945 fLastSelected
= fFirstSelected
;
1946 for (int32 i
= from
; i
<= to
; i
++) {
1947 if (ItemAt(i
)->IsSelected())
1954 BListView::_RecalcItemTops(int32 start
, int32 end
)
1956 int32 count
= CountItems();
1957 if ((start
< 0) || (start
>= count
))
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
);
1968 top
+= ceilf(item
->Height());
1974 BListView::_DoneTracking(BPoint where
)
1976 fTrack
->try_drag
= false;
1977 fTrack
->is_dragging
= false;
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();