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
}
84 BListView::BListView(BRect frame
, const char* name
, list_view_type type
,
85 uint32 resizingMode
, uint32 flags
)
87 BView(frame
, name
, resizingMode
, flags
)
93 BListView::BListView(const char* name
, list_view_type type
, uint32 flags
)
101 BListView::BListView(list_view_type type
)
103 BView(NULL
, B_WILL_DRAW
| B_FRAME_EVENTS
| B_NAVIGABLE
)
109 BListView::BListView(BMessage
* archive
)
114 archive
->FindInt32("_lv_type", &listType
);
115 _InitObject((list_view_type
)listType
);
119 while (archive
->FindMessage("_l_items", i
++, &subData
) == B_OK
) {
120 BArchivable
* object
= instantiate_object(&subData
);
124 BListItem
* item
= dynamic_cast<BListItem
*>(object
);
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.
149 SetSelectionMessage(NULL
);
157 BListView::Instantiate(BMessage
* archive
)
159 if (validate_instantiation(archive
, "BListView"))
160 return new BListView(archive
);
167 BListView::Archive(BMessage
* data
, bool deep
) const
169 status_t status
= BView::Archive(data
, deep
);
173 status
= data
->AddInt32("_lv_type", fListType
);
174 if (status
== B_OK
&& deep
) {
178 while ((item
= ItemAt(i
++)) != NULL
) {
180 status
= item
->Archive(&subData
, true);
182 status
= data
->AddMessage("_l_items", &subData
);
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
);
203 BListView::Draw(BRect updateRect
)
205 int32 count
= CountItems();
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;
223 BListView::AttachedToWindow()
225 BView::AttachedToWindow();
228 if (!Messenger().IsValid())
229 SetTarget(Window(), NULL
);
236 BListView::DetachedFromWindow()
238 BView::DetachedFromWindow();
243 BListView::AllAttached()
245 BView::AllAttached();
250 BListView::AllDetached()
252 BView::AllDetached();
257 BListView::FrameResized(float newWidth
, float newHeight
)
261 // notify items of new width.
267 BListView::FrameMoved(BPoint newPosition
)
269 BView::FrameMoved(newPosition
);
274 BListView::TargetedByScrollView(BScrollView
* 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.
284 BListView::WindowActivated(bool active
)
286 BView::WindowActivated(active
);
294 BListView::MessageReceived(BMessage
* message
)
296 switch (message
->what
) {
297 case B_MOUSE_WHEEL_CHANGED
:
298 if (!fTrack
->is_dragging
)
299 BView::MessageReceived(message
);
302 case B_COUNT_PROPERTIES
:
303 case B_EXECUTE_PROPERTY
:
307 BPropertyInfo
propInfo(sProperties
);
309 const char* property
;
311 if (message
->GetCurrentSpecifier(NULL
, &specifier
) != B_OK
312 || specifier
.FindString("property", &property
) != B_OK
) {
316 switch (propInfo
.FindMatch(message
, 0, &specifier
, message
->what
,
319 BView::MessageReceived(message
);
324 BMessage
reply(B_REPLY
);
325 reply
.AddInt32("result", CountItems());
326 reply
.AddInt32("error", B_OK
);
328 message
->SendReply(&reply
);
339 for (int32 i
= 0; i
< CountItems(); i
++) {
340 if (ItemAt(i
)->IsSelected())
344 BMessage
reply(B_REPLY
);
345 reply
.AddInt32("result", count
);
346 reply
.AddInt32("error", B_OK
);
348 message
->SendReply(&reply
);
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
);
375 BMessage
reply(B_REPLY
);
378 if (message
->FindBool("data", &select
) == B_OK
&& select
)
379 Select(0, CountItems() - 1, false);
383 reply
.AddInt32("error", B_OK
);
385 message
->SendReply(&reply
);
393 if (fListType
== B_MULTIPLE_SELECTION_LIST
)
394 Select(0, CountItems() - 1, false);
398 BView::MessageReceived(message
);
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()) {
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
);
435 Deselect(fAnchorIndex
);
438 while (fAnchorIndex
> 0
439 && !ItemAt(fAnchorIndex
)->IsEnabled());
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
);
460 Deselect(fAnchorIndex
);
463 while (fAnchorIndex
< lastItem
464 && !ItemAt(fAnchorIndex
)->IsEnabled());
474 Select(0, fAnchorIndex
, true);
477 // select the first enabled item
478 int32 lastItem
= CountItems() - 1;
479 for (int32 i
= 0; i
<= lastItem
; i
++) {
480 if (ItemAt(i
)->IsEnabled()) {
492 Select(fAnchorIndex
, CountItems() - 1, true);
493 fAnchorIndex
= CountItems() - 1;
495 // select the last enabled item
496 for (int32 i
= CountItems() - 1; i
>= 0; i
--) {
497 if (ItemAt(i
)->IsEnabled()) {
509 BPoint
scrollOffset(LeftTop());
510 scrollOffset
.y
= std::max(0.0f
, scrollOffset
.y
- Bounds().Height());
511 ScrollTo(scrollOffset
);
517 BPoint
scrollOffset(LeftTop());
518 if (BListItem
* item
= LastItem()) {
519 scrollOffset
.y
+= Bounds().Height();
520 scrollOffset
.y
= std::min(item
->Bottom() - Bounds().Height(),
523 ScrollTo(scrollOffset
);
533 BView::KeyDown(bytes
, numBytes
);
539 BListView::MouseDown(BPoint where
)
544 Window()->UpdateIfNeeded();
547 BMessage
* message
= Looper()->CurrentMessage();
548 int32 index
= IndexOf(where
);
552 message
->FindInt32("buttons", &buttons
);
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
;
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
) {
578 if (doubleClick
&& index
>= fFirstSelected
&& index
<= fLastSelected
) {
579 fTrack
->drag_start
.Set(INT32_MAX
, INT32_MAX
);
581 return BView::MouseDown(where
);
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
);
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
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
);
606 Select(std::min(index
, fFirstSelected
), std::max(index
,
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())
621 // toggle selection state of clicked item
622 if ((modifiers
& B_COMMAND_KEY
) != 0 && ItemAt(index
)->IsSelected())
627 } else if ((modifiers
& B_COMMAND_KEY
) == 0)
630 BView::MouseDown(where
);
635 BListView::MouseUp(BPoint where
)
637 BView::MouseUp(where
);
642 BListView::MouseMoved(BPoint where
, uint32 code
, const BMessage
* dragMessage
)
644 BView::MouseMoved(where
, code
, dragMessage
);
649 BListView::InitiateDrag(BPoint where
, int32 index
, bool wasSelected
)
659 BListView::ResizeToPreferred()
661 BView::ResizeToPreferred();
666 BListView::GetPreferredSize(float *_width
, float *_height
)
668 int32 count
= CountItems();
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
;
681 *_height
= ItemAt(count
- 1)->Bottom();
683 BView::GetPreferredSize(_width
, _height
);
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));
699 return BView::MaxSize();
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));
716 BListView::MakeFocus(bool focused
)
718 if (IsFocus() == focused
)
721 BView::MakeFocus(focused
);
724 fScrollView
->SetBorderHighlighted(focused
);
729 BListView::SetFont(const BFont
* font
, uint32 mask
)
731 BView::SetFont(font
, mask
);
733 if (Window() != NULL
&& !Window()->InViewTransaction())
739 BListView::ScrollTo(BPoint point
)
741 BView::ScrollTo(point
);
745 // #pragma mark - List ops
749 BListView::AddItem(BListItem
* item
, int32 index
)
751 if (!fList
.AddItem(item
, index
))
754 if (fFirstSelected
!= -1 && index
<= fFirstSelected
)
757 if (fLastSelected
!= -1 && index
<= fLastSelected
)
763 item
->SetTop((index
> 0) ? ItemAt(index
- 1)->Bottom() + 1.0 : 0.0);
765 item
->Update(this, &font
);
766 _RecalcItemTops(index
+ 1);
769 _InvalidateFrom(index
);
777 BListView::AddItem(BListItem
* item
)
779 if (!fList
.AddItem(item
))
782 // No need to adapt selection, as this item is the last in the list
787 int32 index
= CountItems() - 1;
788 item
->SetTop((index
> 0) ? ItemAt(index
- 1)->Bottom() + 1.0 : 0.0);
790 item
->Update(this, &font
);
793 InvalidateItem(CountItems() - 1);
801 BListView::AddList(BList
* list
, int32 index
)
803 if (!fList
.AddList(list
, index
))
806 int32 count
= list
->CountItems();
808 if (fFirstSelected
!= -1 && index
< fFirstSelected
)
809 fFirstSelected
+= count
;
811 if (fLastSelected
!= -1 && index
< fLastSelected
)
812 fLastSelected
+= count
;
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);
826 Invalidate(); // TODO
834 BListView::AddList(BList
* list
)
836 return AddList(list
, CountItems());
841 BListView::RemoveItem(int32 index
)
843 BListItem
* item
= ItemAt(index
);
847 if (item
->IsSelected())
850 if (!fList
.RemoveItem(item
))
853 if (fFirstSelected
!= -1 && index
< fFirstSelected
)
856 if (fLastSelected
!= -1 && index
< fLastSelected
)
859 if (fAnchorIndex
!= -1 && index
< fAnchorIndex
)
862 _RecalcItemTops(index
);
864 _InvalidateFrom(index
);
872 BListView::RemoveItem(BListItem
* item
)
874 return BListView::RemoveItem(IndexOf(item
)) != NULL
;
879 BListView::RemoveItems(int32 index
, int32 count
)
881 if (index
>= fList
.CountItems())
887 if (fAnchorIndex
!= -1 && index
< fAnchorIndex
)
888 fAnchorIndex
= index
;
890 fList
.RemoveItems(index
, count
);
891 if (index
< fList
.CountItems())
892 _RecalcItemTops(index
);
900 BListView::SetSelectionMessage(BMessage
* message
)
902 delete fSelectMessage
;
903 fSelectMessage
= message
;
908 BListView::SetInvocationMessage(BMessage
* message
)
910 BInvoker::SetMessage(message
);
915 BListView::InvocationMessage() const
917 return BInvoker::Message();
922 BListView::InvocationCommand() const
924 return BInvoker::Command();
929 BListView::SelectionMessage() const
931 return fSelectMessage
;
936 BListView::SelectionCommand() const
939 return fSelectMessage
->what
;
946 BListView::SetListType(list_view_type type
)
948 if (fListType
== B_MULTIPLE_SELECTION_LIST
949 && type
== B_SINGLE_SELECTION_LIST
) {
950 Select(CurrentSelection(0));
958 BListView::ListType() const
965 BListView::ItemAt(int32 index
) const
967 return (BListItem
*)fList
.ItemAt(index
);
972 BListView::IndexOf(BListItem
* item
) const
976 int32 index
= IndexOf(BPoint(0.0, item
->Top()));
977 if (index
>= 0 && fList
.ItemAt(index
) == item
)
983 return fList
.IndexOf(item
);
988 BListView::IndexOf(BPoint point
) const
991 int32 high
= fList
.CountItems() - 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
)
1003 else if (point
.y
> frameBottom
)
1014 BListView::FirstItem() const
1016 return (BListItem
*)fList
.FirstItem();
1021 BListView::LastItem() const
1023 return (BListItem
*)fList
.LastItem();
1028 BListView::HasItem(BListItem
*item
) const
1030 return IndexOf(item
) != -1;
1035 BListView::CountItems() const
1037 return fList
.CountItems();
1042 BListView::MakeEmpty()
1044 if (fList
.IsEmpty())
1047 _DeselectAll(-1, -1);
1058 BListView::IsEmpty() const
1060 return fList
.IsEmpty();
1065 BListView::DoForEach(bool (*func
)(BListItem
*))
1067 fList
.DoForEach(reinterpret_cast<bool (*)(void*)>(func
));
1072 BListView::DoForEach(bool (*func
)(BListItem
*, void*), void* arg
)
1074 fList
.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func
), arg
);
1079 BListView::Items() const
1081 return (const BListItem
**)fList
.Items();
1086 BListView::InvalidateItem(int32 index
)
1088 Invalidate(ItemFrame(index
));
1093 BListView::ScrollToSelection()
1095 BRect itemFrame
= ItemFrame(CurrentSelection(0));
1097 if (Bounds().Contains(itemFrame
))
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
);
1111 BListView::Select(int32 index
, bool extend
)
1113 if (_Select(index
, extend
)) {
1115 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1121 BListView::Select(int32 start
, int32 finish
, bool extend
)
1123 if (_Select(start
, finish
, extend
)) {
1125 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1131 BListView::IsItemSelected(int32 index
) const
1133 BListItem
* item
= ItemAt(index
);
1135 return item
->IsSelected();
1142 BListView::CurrentSelection(int32 index
) const
1144 if (fFirstSelected
== -1)
1148 return fFirstSelected
;
1150 for (int32 i
= fFirstSelected
; i
<= fLastSelected
; i
++) {
1151 if (ItemAt(i
)->IsSelected()) {
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(¬ify
);
1172 BMessage
clone(kind
);
1173 status_t err
= B_BAD_VALUE
;
1175 if (!message
&& !notify
)
1176 message
= 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
);
1191 if (fFirstSelected
>= 0) {
1192 for (int32 i
= fFirstSelected
; i
<= fLastSelected
; i
++) {
1193 if (ItemAt(i
)->IsSelected())
1194 clone
.AddInt32("index", i
);
1200 err
= BInvoker::Invoke(&clone
);
1202 SendNotices(kind
, &clone
);
1209 BListView::DeselectAll()
1211 if (_DeselectAll(-1, -1)) {
1213 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1219 BListView::DeselectExcept(int32 exceptFrom
, int32 exceptTo
)
1221 if (exceptFrom
> exceptTo
|| exceptFrom
< 0 || exceptTo
< 0)
1224 if (_DeselectAll(exceptFrom
, exceptTo
)) {
1226 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1232 BListView::Deselect(int32 index
)
1234 if (_Deselect(index
)) {
1236 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1242 BListView::SelectionChanged()
1244 // Hook method to be implemented by subclasses
1249 BListView::SortItems(int (*cmp
)(const void *, const void *))
1251 if (_DeselectAll(-1, -1)) {
1253 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1256 fList
.SortItems(cmp
);
1263 BListView::SwapItems(int32 a
, int32 b
)
1270 return DoMiscellaneous(B_SWAP_OP
, &data
);
1275 BListView::MoveItem(int32 from
, int32 to
)
1279 data
.move
.from
= from
;
1282 return DoMiscellaneous(B_MOVE_OP
, &data
);
1287 BListView::ReplaceItem(int32 index
, BListItem
* item
)
1291 data
.replace
.index
= index
;
1292 data
.replace
.item
= item
;
1294 return DoMiscellaneous(B_REPLACE_OP
, &data
);
1299 BListView::ItemFrame(int32 index
)
1301 BRect frame
= Bounds();
1302 if (index
< 0 || index
>= CountItems()) {
1306 BListItem
* item
= ItemAt(index
);
1307 frame
.top
= item
->Top();
1308 frame
.bottom
= item
->Bottom();
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
,
1328 // TODO: msg->AddInt32("_match_code_", );
1335 BListView::GetSupportedSuites(BMessage
* data
)
1340 status_t err
= data
->AddString("suites", "suite/vnd.Be-list-view");
1342 BPropertyInfo
propertyInfo(sProperties
);
1344 err
= data
->AddFlat("messages", &propertyInfo
);
1347 return BView::GetSupportedSuites(data
);
1353 BListView::Perform(perform_code code
, void* _data
)
1356 case PERFORM_CODE_MIN_SIZE
:
1357 ((perform_data_min_size
*)_data
)->return_value
1358 = BListView::MinSize();
1360 case PERFORM_CODE_MAX_SIZE
:
1361 ((perform_data_max_size
*)_data
)->return_value
1362 = BListView::MaxSize();
1364 case PERFORM_CODE_PREFERRED_SIZE
:
1365 ((perform_data_preferred_size
*)_data
)->return_value
1366 = BListView::PreferredSize();
1368 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
1369 ((perform_data_layout_alignment
*)_data
)->return_value
1370 = BListView::LayoutAlignment();
1372 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
1373 ((perform_data_has_height_for_width
*)_data
)->return_value
1374 = BListView::HasHeightForWidth();
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
,
1384 case PERFORM_CODE_SET_LAYOUT
:
1386 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
1387 BListView::SetLayout(data
->layout
);
1390 case PERFORM_CODE_LAYOUT_INVALIDATED
:
1392 perform_data_layout_invalidated
* data
1393 = (perform_data_layout_invalidated
*)_data
;
1394 BListView::LayoutInvalidated(data
->descendants
);
1397 case PERFORM_CODE_DO_LAYOUT
:
1399 BListView::DoLayout();
1404 return BView::Perform(code
, _data
);
1409 BListView::DoMiscellaneous(MiscCode code
, MiscData
* data
)
1411 if (code
> B_SWAP_OP
)
1419 return _ReplaceItem(data
->replace
.index
, data
->replace
.item
);
1422 return _MoveItem(data
->move
.from
, data
->move
.to
);
1425 return _SwapItems(data
->swap
.a
, data
->swap
.b
);
1435 void BListView::_ReservedListView2() {}
1436 void BListView::_ReservedListView3() {}
1437 void BListView::_ReservedListView4() {}
1441 BListView::operator=(const BListView
& /*other*/)
1451 BListView::_InitObject(list_view_type type
)
1454 fFirstSelected
= -1;
1457 fSelectMessage
= 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
);
1474 BListView::_FixupScrollBar()
1476 BScrollBar
* vertScroller
= ScrollBar(B_VERTICAL
);
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
) {
1490 vertScroller
->SetRange(0.0, 0.0);
1491 vertScroller
->SetValue(0.0);
1492 // also scrolls ListView to the top
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
);
1502 vertScroller
->SetSteps(ceilf(FirstItem()->Height()), bounds
.Height());
1507 BListView::_InvalidateFrom(int32 index
)
1509 // make sure index is behind last valid index
1510 int32 count
= CountItems();
1514 // take the item before the wanted one,
1515 // because that might already be removed
1517 BRect dirty
= Bounds();
1519 dirty
.top
= ItemFrame(index
).bottom
+ 1;
1526 BListView::_FontChanged()
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.
1542 BListView::_Select(int32 index
, bool extend
)
1544 if (index
< 0 || index
>= CountItems())
1547 // only lock the window when there is one
1548 BAutolock
locker(Window());
1549 if (Window() != NULL
&& !locker
.IsLocked())
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,
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
;
1578 if (Window() != NULL
)
1579 InvalidateItem(index
);
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.
1591 BListView::_Select(int32 from
, int32 to
, bool extend
)
1596 BAutolock
locker(Window());
1597 if (Window() && !locker
.IsLocked())
1600 bool changed
= false;
1602 if (fFirstSelected
!= -1 && !extend
)
1603 changed
= _DeselectAll(from
, to
);
1605 if (fFirstSelected
== -1) {
1606 fFirstSelected
= from
;
1609 if (from
< fFirstSelected
)
1610 fFirstSelected
= from
;
1611 if (to
> fLastSelected
)
1615 for (int32 i
= from
; i
<= to
; ++i
) {
1616 BListItem
* item
= ItemAt(i
);
1617 if (item
!= NULL
&& !item
->IsSelected() && item
->IsEnabled()) {
1619 if (Window() != NULL
)
1630 BListView::_Deselect(int32 index
)
1632 if (index
< 0 || index
>= CountItems())
1635 BWindow
* window
= Window();
1636 BAutolock
locker(window
);
1637 if (window
!= NULL
&& !locker
.IsLocked())
1640 BListItem
* item
= ItemAt(index
);
1642 if (item
!= NULL
&& item
->IsSelected()) {
1643 BRect
frame(ItemFrame(index
));
1644 BRect
bounds(Bounds());
1648 if (fFirstSelected
== index
&& fLastSelected
== index
) {
1649 fFirstSelected
= -1;
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);
1668 BListView::_DeselectAll(int32 exceptFrom
, int32 exceptTo
)
1670 if (fFirstSelected
== -1)
1673 BAutolock
locker(Window());
1674 if (Window() && !locker
.IsLocked())
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
)
1684 BListItem
* item
= ItemAt(index
);
1685 if (item
!= NULL
&& item
->IsSelected()) {
1687 InvalidateItem(index
);
1695 if (exceptFrom
!= -1) {
1696 fFirstSelected
= _CalcFirstSelected(exceptFrom
);
1697 fLastSelected
= _CalcLastSelected(exceptTo
);
1699 fFirstSelected
= fLastSelected
= -1;
1706 BListView::_CalcFirstSelected(int32 after
)
1708 if (after
>= CountItems())
1711 int32 count
= CountItems();
1712 for (int32 i
= after
; i
< count
; i
++) {
1713 if (ItemAt(i
)->IsSelected())
1722 BListView::_CalcLastSelected(int32 before
)
1727 before
= std::min(CountItems() - 1, before
);
1729 for (int32 i
= before
; i
>= 0; i
--) {
1730 if (ItemAt(i
)->IsSelected())
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
);
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
));
1753 SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR
));
1755 item
->DrawItem(this, itemRect
, complete
);
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
))
1772 // nothing to do, but success nevertheless
1776 // track anchor item
1777 if (fAnchorIndex
== a
)
1779 else if (fAnchorIndex
== b
)
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
1797 ItemAt(a
)->SetTop(aFrame
.top
);
1798 ItemAt(b
)->SetTop(bFrame
.top
);
1800 // take care of invalidation
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
);
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
))
1827 // track anchor item
1828 if (fAnchorIndex
== from
)
1832 if (ItemAt(to
)->IsSelected()) {
1833 _RescanSelection(from
, to
);
1834 // though the actually selected items stayed the
1835 // same, the selection has still changed
1839 _RecalcItemTops((to
> from
) ? from
: to
);
1841 // take care of invalidation
1843 // NOTE: window looper is assumed to be locked!
1844 Invalidate(frameFrom
| frameTo
);
1852 BListView::_ReplaceItem(int32 index
, BListItem
* item
)
1857 BListItem
* old
= ItemAt(index
);
1861 BRect frame
= ItemFrame(index
);
1863 bool selectionChanged
= old
->IsSelected() != item
->IsSelected();
1866 if (!fList
.ReplaceItem(index
, item
))
1870 if (selectionChanged
) {
1871 int32 start
= std::min(fFirstSelected
, index
);
1872 int32 end
= std::max(fLastSelected
, index
);
1873 _RescanSelection(start
, end
);
1876 _RecalcItemTops(index
);
1878 bool itemHeightChanged
= frame
!= ItemFrame(index
);
1880 // take care of invalidation
1882 // NOTE: window looper is assumed to be locked!
1883 if (itemHeightChanged
)
1884 _InvalidateFrom(index
);
1889 if (itemHeightChanged
)
1897 BListView::_RescanSelection(int32 from
, int32 to
)
1905 from
= std::max((int32
)0, from
);
1906 to
= std::min(to
, CountItems() - 1);
1908 if (fAnchorIndex
!= -1) {
1909 if (fAnchorIndex
== from
)
1911 else if (fAnchorIndex
== to
)
1912 fAnchorIndex
= from
;
1915 for (int32 i
= from
; i
<= to
; i
++) {
1916 if (ItemAt(i
)->IsSelected()) {
1922 if (fFirstSelected
> from
)
1923 from
= fFirstSelected
;
1925 fLastSelected
= fFirstSelected
;
1926 for (int32 i
= from
; i
<= to
; i
++) {
1927 if (ItemAt(i
)->IsSelected())
1934 BListView::_RecalcItemTops(int32 start
, int32 end
)
1936 int32 count
= CountItems();
1937 if ((start
< 0) || (start
>= count
))
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
);
1948 top
+= ceilf(item
->Height());
1954 BListView::_DoneTracking(BPoint where
)
1956 fTrack
->try_drag
= false;
1957 fTrack
->is_dragging
= false;
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();