2 * Copyright 2001-2013 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
19 #include <LayoutUtils.h>
20 #include <PropertyInfo.h>
21 #include <ScrollBar.h>
22 #include <ScrollView.h>
25 #include <binary_compatibility/Interface.h>
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,
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 "
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,
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
)
89 BListView::BListView(const char* name
, list_view_type type
, uint32 flags
)
97 BListView::BListView(list_view_type type
)
99 BView(NULL
, B_WILL_DRAW
| B_FRAME_EVENTS
| B_NAVIGABLE
)
105 BListView::BListView(BMessage
* archive
)
110 archive
->FindInt32("_lv_type", &listType
);
111 _InitObject((list_view_type
)listType
);
115 while (archive
->FindMessage("_l_items", i
++, &subData
) == B_OK
) {
116 BArchivable
* object
= instantiate_object(&subData
);
120 BListItem
* item
= dynamic_cast<BListItem
*>(object
);
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.
145 SetSelectionMessage(NULL
);
153 BListView::Instantiate(BMessage
* archive
)
155 if (validate_instantiation(archive
, "BListView"))
156 return new BListView(archive
);
163 BListView::Archive(BMessage
* data
, bool deep
) const
165 status_t status
= BView::Archive(data
, deep
);
169 status
= data
->AddInt32("_lv_type", fListType
);
170 if (status
== B_OK
&& deep
) {
174 while ((item
= ItemAt(i
++)) != NULL
) {
176 status
= item
->Archive(&subData
, true);
178 status
= data
->AddMessage("_l_items", &subData
);
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
);
199 BListView::Draw(BRect updateRect
)
201 int32 count
= CountItems();
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;
219 BListView::AttachedToWindow()
221 BView::AttachedToWindow();
224 if (!Messenger().IsValid())
225 SetTarget(Window(), NULL
);
232 BListView::DetachedFromWindow()
234 BView::DetachedFromWindow();
239 BListView::AllAttached()
241 BView::AllAttached();
246 BListView::AllDetached()
248 BView::AllDetached();
253 BListView::FrameResized(float newWidth
, float newHeight
)
260 BListView::FrameMoved(BPoint newPosition
)
262 BView::FrameMoved(newPosition
);
267 BListView::TargetedByScrollView(BScrollView
* 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.
277 BListView::WindowActivated(bool active
)
279 BView::WindowActivated(active
);
287 BListView::MessageReceived(BMessage
* message
)
289 switch (message
->what
) {
290 case B_COUNT_PROPERTIES
:
291 case B_EXECUTE_PROPERTY
:
295 BPropertyInfo
propInfo(sProperties
);
297 const char *property
;
299 if (message
->GetCurrentSpecifier(NULL
, &specifier
) != B_OK
300 || specifier
.FindString("property", &property
) != B_OK
)
303 switch (propInfo
.FindMatch(message
, 0, &specifier
, message
->what
,
306 BView::MessageReceived(message
);
311 BMessage
reply(B_REPLY
);
312 reply
.AddInt32("result", CountItems());
313 reply
.AddInt32("error", B_OK
);
315 message
->SendReply(&reply
);
326 for (int32 i
= 0; i
< CountItems(); i
++) {
327 if (ItemAt(i
)->IsSelected())
331 BMessage
reply(B_REPLY
);
332 reply
.AddInt32("result", count
);
333 reply
.AddInt32("error", B_OK
);
335 message
->SendReply(&reply
);
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
);
362 BMessage
reply(B_REPLY
);
365 if (message
->FindBool("data", &select
) == B_OK
&& select
)
366 Select(0, CountItems() - 1, false);
370 reply
.AddInt32("error", B_OK
);
372 message
->SendReply(&reply
);
380 if (fListType
== B_MULTIPLE_SELECTION_LIST
)
381 Select(0, CountItems() - 1, false);
385 BView::MessageReceived(message
);
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()) {
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
);
422 Deselect(fAnchorIndex
);
425 while (fAnchorIndex
> 0
426 && !ItemAt(fAnchorIndex
)->IsEnabled());
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
);
447 Deselect(fAnchorIndex
);
450 while (fAnchorIndex
< lastItem
451 && !ItemAt(fAnchorIndex
)->IsEnabled());
461 Select(0, fAnchorIndex
, true);
464 // select the first enabled item
465 int32 lastItem
= CountItems() - 1;
466 for (int32 i
= 0; i
<= lastItem
; i
++) {
467 if (ItemAt(i
)->IsEnabled()) {
479 Select(fAnchorIndex
, CountItems() - 1, true);
480 fAnchorIndex
= CountItems() - 1;
482 // select the last enabled item
483 for (int32 i
= CountItems() - 1; i
>= 0; i
--) {
484 if (ItemAt(i
)->IsEnabled()) {
496 BPoint
scrollOffset(LeftTop());
497 scrollOffset
.y
= max_c(0, scrollOffset
.y
- Bounds().Height());
498 ScrollTo(scrollOffset
);
504 BPoint
scrollOffset(LeftTop());
505 if (BListItem
* item
= LastItem()) {
506 scrollOffset
.y
+= Bounds().Height();
507 scrollOffset
.y
= min_c(item
->Bottom() - Bounds().Height(),
510 ScrollTo(scrollOffset
);
520 BView::KeyDown(bytes
, numBytes
);
526 BListView::MouseDown(BPoint point
)
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
;
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
) {
557 if (doubleClick
&& index
>= fFirstSelected
&& index
<= fLastSelected
) {
558 fTrack
->drag_start
.Set(INT32_MAX
, INT32_MAX
);
564 message
->FindInt32("modifiers", &modifiers
);
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;
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
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
);
585 Select(min_c(index
, fFirstSelected
), max_c(index
,
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())
600 // toggle selection state of clicked item
601 if ((modifiers
& B_COMMAND_KEY
) && ItemAt(index
)->IsSelected())
606 } else if ((modifiers
& B_COMMAND_KEY
) == 0)
612 BListView::MouseUp(BPoint where
)
614 fTrack
->try_drag
= false;
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
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
);
639 BListView::InitiateDrag(BPoint point
, int32 index
, bool wasSelected
)
649 BListView::ResizeToPreferred()
651 BView::ResizeToPreferred();
656 BListView::GetPreferredSize(float *_width
, float *_height
)
658 int32 count
= CountItems();
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
;
671 *_height
= ItemAt(count
- 1)->Bottom();
673 BView::GetPreferredSize(_width
, _height
);
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));
689 return BView::MaxSize();
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));
706 BListView::MakeFocus(bool focused
)
708 if (IsFocus() == focused
)
711 BView::MakeFocus(focused
);
714 fScrollView
->SetBorderHighlighted(focused
);
719 BListView::SetFont(const BFont
* font
, uint32 mask
)
721 BView::SetFont(font
, mask
);
723 if (Window() != NULL
&& !Window()->InViewTransaction())
729 BListView::ScrollTo(BPoint point
)
731 BView::ScrollTo(point
);
735 // #pragma mark - List ops
739 BListView::AddItem(BListItem
* item
, int32 index
)
741 if (!fList
.AddItem(item
, index
))
744 if (fFirstSelected
!= -1 && index
<= fFirstSelected
)
747 if (fLastSelected
!= -1 && index
<= fLastSelected
)
753 item
->SetTop((index
> 0) ? ItemAt(index
- 1)->Bottom() + 1.0 : 0.0);
755 item
->Update(this, &font
);
756 _RecalcItemTops(index
+ 1);
759 _InvalidateFrom(index
);
767 BListView::AddItem(BListItem
* item
)
769 if (!fList
.AddItem(item
))
772 // No need to adapt selection, as this item is the last in the list
777 int32 index
= CountItems() - 1;
778 item
->SetTop((index
> 0) ? ItemAt(index
- 1)->Bottom() + 1.0 : 0.0);
780 item
->Update(this, &font
);
783 InvalidateItem(CountItems() - 1);
791 BListView::AddList(BList
* list
, int32 index
)
793 if (!fList
.AddList(list
, index
))
796 int32 count
= list
->CountItems();
798 if (fFirstSelected
!= -1 && index
< fFirstSelected
)
799 fFirstSelected
+= count
;
801 if (fLastSelected
!= -1 && index
< fLastSelected
)
802 fLastSelected
+= count
;
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);
816 Invalidate(); // TODO
824 BListView::AddList(BList
* list
)
826 return AddList(list
, CountItems());
831 BListView::RemoveItem(int32 index
)
833 BListItem
* item
= ItemAt(index
);
837 if (item
->IsSelected())
840 if (!fList
.RemoveItem(item
))
843 if (fFirstSelected
!= -1 && index
< fFirstSelected
)
846 if (fLastSelected
!= -1 && index
< fLastSelected
)
849 if (fAnchorIndex
!= -1 && index
< fAnchorIndex
)
852 _RecalcItemTops(index
);
854 _InvalidateFrom(index
);
862 BListView::RemoveItem(BListItem
* item
)
864 return BListView::RemoveItem(IndexOf(item
)) != NULL
;
869 BListView::RemoveItems(int32 index
, int32 count
)
871 if (index
>= fList
.CountItems())
877 if (fAnchorIndex
!= -1 && index
< fAnchorIndex
)
878 fAnchorIndex
= index
;
880 fList
.RemoveItems(index
, count
);
881 if (index
< fList
.CountItems())
882 _RecalcItemTops(index
);
890 BListView::SetSelectionMessage(BMessage
* message
)
892 delete fSelectMessage
;
893 fSelectMessage
= message
;
898 BListView::SetInvocationMessage(BMessage
* message
)
900 BInvoker::SetMessage(message
);
905 BListView::InvocationMessage() const
907 return BInvoker::Message();
912 BListView::InvocationCommand() const
914 return BInvoker::Command();
919 BListView::SelectionMessage() const
921 return fSelectMessage
;
926 BListView::SelectionCommand() const
929 return fSelectMessage
->what
;
936 BListView::SetListType(list_view_type type
)
938 if (fListType
== B_MULTIPLE_SELECTION_LIST
939 && type
== B_SINGLE_SELECTION_LIST
) {
940 Select(CurrentSelection(0));
948 BListView::ListType() const
955 BListView::ItemAt(int32 index
) const
957 return (BListItem
*)fList
.ItemAt(index
);
962 BListView::IndexOf(BListItem
* item
) const
966 int32 index
= IndexOf(BPoint(0.0, item
->Top()));
967 if (index
>= 0 && fList
.ItemAt(index
) == item
)
973 return fList
.IndexOf(item
);
978 BListView::IndexOf(BPoint point
) const
981 int32 high
= fList
.CountItems() - 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
)
993 else if (point
.y
> frameBottom
)
1004 BListView::FirstItem() const
1006 return (BListItem
*)fList
.FirstItem();
1011 BListView::LastItem() const
1013 return (BListItem
*)fList
.LastItem();
1018 BListView::HasItem(BListItem
*item
) const
1020 return IndexOf(item
) != -1;
1025 BListView::CountItems() const
1027 return fList
.CountItems();
1032 BListView::MakeEmpty()
1034 if (fList
.IsEmpty())
1037 _DeselectAll(-1, -1);
1048 BListView::IsEmpty() const
1050 return fList
.IsEmpty();
1055 BListView::DoForEach(bool (*func
)(BListItem
*))
1057 fList
.DoForEach(reinterpret_cast<bool (*)(void*)>(func
));
1062 BListView::DoForEach(bool (*func
)(BListItem
*, void*), void* arg
)
1064 fList
.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func
), arg
);
1069 BListView::Items() const
1071 return (const BListItem
**)fList
.Items();
1076 BListView::InvalidateItem(int32 index
)
1078 Invalidate(ItemFrame(index
));
1083 BListView::ScrollToSelection()
1085 BRect itemFrame
= ItemFrame(CurrentSelection(0));
1087 if (Bounds().Contains(itemFrame
))
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
);
1101 BListView::Select(int32 index
, bool extend
)
1103 if (_Select(index
, extend
)) {
1105 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1111 BListView::Select(int32 start
, int32 finish
, bool extend
)
1113 if (_Select(start
, finish
, extend
)) {
1115 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1121 BListView::IsItemSelected(int32 index
) const
1123 BListItem
* item
= ItemAt(index
);
1125 return item
->IsSelected();
1132 BListView::CurrentSelection(int32 index
) const
1134 if (fFirstSelected
== -1)
1138 return fFirstSelected
;
1140 for (int32 i
= fFirstSelected
; i
<= fLastSelected
; i
++) {
1141 if (ItemAt(i
)->IsSelected()) {
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(¬ify
);
1162 BMessage
clone(kind
);
1163 status_t err
= B_BAD_VALUE
;
1165 if (!message
&& !notify
)
1166 message
= 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
);
1181 if (fFirstSelected
>= 0) {
1182 for (int32 i
= fFirstSelected
; i
<= fLastSelected
; i
++) {
1183 if (ItemAt(i
)->IsSelected())
1184 clone
.AddInt32("index", i
);
1190 err
= BInvoker::Invoke(&clone
);
1192 SendNotices(kind
, &clone
);
1199 BListView::DeselectAll()
1201 if (_DeselectAll(-1, -1)) {
1203 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1209 BListView::DeselectExcept(int32 exceptFrom
, int32 exceptTo
)
1211 if (exceptFrom
> exceptTo
|| exceptFrom
< 0 || exceptTo
< 0)
1214 if (_DeselectAll(exceptFrom
, exceptTo
)) {
1216 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1222 BListView::Deselect(int32 index
)
1224 if (_Deselect(index
)) {
1226 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1232 BListView::SelectionChanged()
1234 // Hook method to be implemented by subclasses
1239 BListView::SortItems(int (*cmp
)(const void *, const void *))
1241 if (_DeselectAll(-1, -1)) {
1243 InvokeNotify(fSelectMessage
, B_CONTROL_MODIFIED
);
1246 fList
.SortItems(cmp
);
1253 BListView::SwapItems(int32 a
, int32 b
)
1260 return DoMiscellaneous(B_SWAP_OP
, &data
);
1265 BListView::MoveItem(int32 from
, int32 to
)
1269 data
.move
.from
= from
;
1272 return DoMiscellaneous(B_MOVE_OP
, &data
);
1277 BListView::ReplaceItem(int32 index
, BListItem
* item
)
1281 data
.replace
.index
= index
;
1282 data
.replace
.item
= item
;
1284 return DoMiscellaneous(B_REPLACE_OP
, &data
);
1289 BListView::ItemFrame(int32 index
)
1291 BRect frame
= Bounds();
1292 if (index
< 0 || index
>= CountItems()) {
1296 BListItem
* item
= ItemAt(index
);
1297 frame
.top
= item
->Top();
1298 frame
.bottom
= item
->Bottom();
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
,
1318 // TODO: msg->AddInt32("_match_code_", );
1325 BListView::GetSupportedSuites(BMessage
* data
)
1330 status_t err
= data
->AddString("suites", "suite/vnd.Be-list-view");
1332 BPropertyInfo
propertyInfo(sProperties
);
1334 err
= data
->AddFlat("messages", &propertyInfo
);
1337 return BView::GetSupportedSuites(data
);
1343 BListView::Perform(perform_code code
, void* _data
)
1346 case PERFORM_CODE_MIN_SIZE
:
1347 ((perform_data_min_size
*)_data
)->return_value
1348 = BListView::MinSize();
1350 case PERFORM_CODE_MAX_SIZE
:
1351 ((perform_data_max_size
*)_data
)->return_value
1352 = BListView::MaxSize();
1354 case PERFORM_CODE_PREFERRED_SIZE
:
1355 ((perform_data_preferred_size
*)_data
)->return_value
1356 = BListView::PreferredSize();
1358 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
1359 ((perform_data_layout_alignment
*)_data
)->return_value
1360 = BListView::LayoutAlignment();
1362 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
1363 ((perform_data_has_height_for_width
*)_data
)->return_value
1364 = BListView::HasHeightForWidth();
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
,
1374 case PERFORM_CODE_SET_LAYOUT
:
1376 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
1377 BListView::SetLayout(data
->layout
);
1380 case PERFORM_CODE_LAYOUT_INVALIDATED
:
1382 perform_data_layout_invalidated
* data
1383 = (perform_data_layout_invalidated
*)_data
;
1384 BListView::LayoutInvalidated(data
->descendants
);
1387 case PERFORM_CODE_DO_LAYOUT
:
1389 BListView::DoLayout();
1394 return BView::Perform(code
, _data
);
1399 BListView::DoMiscellaneous(MiscCode code
, MiscData
* data
)
1401 if (code
> B_SWAP_OP
)
1409 return _ReplaceItem(data
->replace
.index
, data
->replace
.item
);
1412 return _MoveItem(data
->move
.from
, data
->move
.to
);
1415 return _SwapItems(data
->swap
.a
, data
->swap
.b
);
1425 void BListView::_ReservedListView2() {}
1426 void BListView::_ReservedListView3() {}
1427 void BListView::_ReservedListView4() {}
1431 BListView::operator=(const BListView
& /*other*/)
1441 BListView::_InitObject(list_view_type type
)
1444 fFirstSelected
= -1;
1447 fSelectMessage
= 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
));
1459 BListView::_FixupScrollBar()
1461 BScrollBar
* vertScroller
= ScrollBar(B_VERTICAL
);
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
) {
1475 vertScroller
->SetRange(0.0, 0.0);
1476 vertScroller
->SetValue(0.0);
1477 // also scrolls ListView to the top
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
);
1487 vertScroller
->SetSteps(ceilf(FirstItem()->Height()), bounds
.Height());
1492 BListView::_InvalidateFrom(int32 index
)
1494 // make sure index is behind last valid index
1495 int32 count
= CountItems();
1499 // take the item before the wanted one,
1500 // because that might already be removed
1502 BRect dirty
= Bounds();
1504 dirty
.top
= ItemFrame(index
).bottom
+ 1;
1511 BListView::_FontChanged()
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.
1527 BListView::_Select(int32 index
, bool extend
)
1529 if (index
< 0 || index
>= CountItems())
1532 // only lock the window when there is one
1533 BAutolock
locker(Window());
1534 if (Window() != NULL
&& !locker
.IsLocked())
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,
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
;
1563 if (Window() != NULL
)
1564 InvalidateItem(index
);
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.
1576 BListView::_Select(int32 from
, int32 to
, bool extend
)
1581 BAutolock
locker(Window());
1582 if (Window() && !locker
.IsLocked())
1585 bool changed
= false;
1587 if (fFirstSelected
!= -1 && !extend
)
1588 changed
= _DeselectAll(from
, to
);
1590 if (fFirstSelected
== -1) {
1591 fFirstSelected
= from
;
1594 if (from
< fFirstSelected
)
1595 fFirstSelected
= from
;
1596 if (to
> fLastSelected
)
1600 for (int32 i
= from
; i
<= to
; ++i
) {
1601 BListItem
* item
= ItemAt(i
);
1602 if (item
!= NULL
&& !item
->IsSelected() && item
->IsEnabled()) {
1604 if (Window() != NULL
)
1615 BListView::_Deselect(int32 index
)
1617 if (index
< 0 || index
>= CountItems())
1620 BWindow
* window
= Window();
1621 BAutolock
locker(window
);
1622 if (window
!= NULL
&& !locker
.IsLocked())
1625 BListItem
* item
= ItemAt(index
);
1627 if (item
!= NULL
&& item
->IsSelected()) {
1628 BRect
frame(ItemFrame(index
));
1629 BRect
bounds(Bounds());
1633 if (fFirstSelected
== index
&& fLastSelected
== index
) {
1634 fFirstSelected
= -1;
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);
1653 BListView::_DeselectAll(int32 exceptFrom
, int32 exceptTo
)
1655 if (fFirstSelected
== -1)
1658 BAutolock
locker(Window());
1659 if (Window() && !locker
.IsLocked())
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
)
1669 BListItem
* item
= ItemAt(index
);
1670 if (item
!= NULL
&& item
->IsSelected()) {
1672 InvalidateItem(index
);
1680 if (exceptFrom
!= -1) {
1681 fFirstSelected
= _CalcFirstSelected(exceptFrom
);
1682 fLastSelected
= _CalcLastSelected(exceptTo
);
1684 fFirstSelected
= fLastSelected
= -1;
1691 BListView::_CalcFirstSelected(int32 after
)
1693 if (after
>= CountItems())
1696 int32 count
= CountItems();
1697 for (int32 i
= after
; i
< count
; i
++) {
1698 if (ItemAt(i
)->IsSelected())
1707 BListView::_CalcLastSelected(int32 before
)
1712 before
= min_c(CountItems() - 1, before
);
1714 for (int32 i
= before
; i
>= 0; i
--) {
1715 if (ItemAt(i
)->IsSelected())
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
);
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
));
1738 SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR
));
1740 item
->DrawItem(this, itemRect
, complete
);
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
))
1757 // nothing to do, but success nevertheless
1761 // track anchor item
1762 if (fAnchorIndex
== a
)
1764 else if (fAnchorIndex
== b
)
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
1782 ItemAt(a
)->SetTop(aFrame
.top
);
1783 ItemAt(b
)->SetTop(bFrame
.top
);
1785 // take care of invalidation
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
);
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
))
1812 // track anchor item
1813 if (fAnchorIndex
== from
)
1817 if (ItemAt(to
)->IsSelected()) {
1818 _RescanSelection(from
, to
);
1819 // though the actually selected items stayed the
1820 // same, the selection has still changed
1824 _RecalcItemTops((to
> from
) ? from
: to
);
1826 // take care of invalidation
1828 // NOTE: window looper is assumed to be locked!
1829 Invalidate(frameFrom
| frameTo
);
1837 BListView::_ReplaceItem(int32 index
, BListItem
* item
)
1842 BListItem
* old
= ItemAt(index
);
1846 BRect frame
= ItemFrame(index
);
1848 bool selectionChanged
= old
->IsSelected() != item
->IsSelected();
1851 if (!fList
.ReplaceItem(index
, item
))
1855 if (selectionChanged
) {
1856 int32 start
= min_c(fFirstSelected
, index
);
1857 int32 end
= max_c(fLastSelected
, index
);
1858 _RescanSelection(start
, end
);
1861 _RecalcItemTops(index
);
1863 bool itemHeightChanged
= frame
!= ItemFrame(index
);
1865 // take care of invalidation
1867 // NOTE: window looper is assumed to be locked!
1868 if (itemHeightChanged
)
1869 _InvalidateFrom(index
);
1874 if (itemHeightChanged
)
1882 BListView::_RescanSelection(int32 from
, int32 to
)
1890 from
= max_c(0, from
);
1891 to
= min_c(to
, CountItems() - 1);
1893 if (fAnchorIndex
!= -1) {
1894 if (fAnchorIndex
== from
)
1896 else if (fAnchorIndex
== to
)
1897 fAnchorIndex
= from
;
1900 for (int32 i
= from
; i
<= to
; i
++) {
1901 if (ItemAt(i
)->IsSelected()) {
1907 if (fFirstSelected
> from
)
1908 from
= fFirstSelected
;
1910 fLastSelected
= fFirstSelected
;
1911 for (int32 i
= from
; i
<= to
; i
++) {
1912 if (ItemAt(i
)->IsSelected())
1919 BListView::_RecalcItemTops(int32 start
, int32 end
)
1921 int32 count
= CountItems();
1922 if ((start
< 0) || (start
>= count
))
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
);
1933 top
+= ceilf(item
->Height());