#include <ListView.h>
#include <algorithm>
#include <stdio.h>
#include <Autolock.h>
#include <LayoutUtils.h>
#include <PropertyInfo.h>
#include <ScrollBar.h>
#include <ScrollView.h>
#include <Window.h>
#include <binary_compatibility/Interface.h>
struct track_data {
BPoint drag_start;
int32 item_index;
int32 buttons;
uint32 selected_click_count;
bool was_selected;
bool try_drag;
bool is_dragging;
bigtime_t last_click_time;
};
const float kDoubleClickThreshold = 6.0f;
static property_info sProperties[] = {
{ "Item", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
"Returns the number of BListItems currently in the list.", 0,
{ B_INT32_TYPE }
},
{ "Item", { B_EXECUTE_PROPERTY, 0 }, { B_INDEX_SPECIFIER,
B_REVERSE_INDEX_SPECIFIER, B_RANGE_SPECIFIER,
B_REVERSE_RANGE_SPECIFIER, 0 },
"Select and invoke the specified items, first removing any existing "
"selection."
},
{ "Selection", { B_COUNT_PROPERTIES, 0 }, { B_DIRECT_SPECIFIER, 0 },
"Returns int32 count of items in the selection.", 0, { B_INT32_TYPE }
},
{ "Selection", { B_EXECUTE_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
"Invoke items in selection."
},
{ "Selection", { B_GET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
"Returns int32 indices of all items in the selection.", 0,
{ B_INT32_TYPE }
},
{ "Selection", { B_SET_PROPERTY, 0 }, { B_INDEX_SPECIFIER,
B_REVERSE_INDEX_SPECIFIER, B_RANGE_SPECIFIER,
B_REVERSE_RANGE_SPECIFIER, 0 },
"Extends current selection or deselects specified items. Boolean field "
"\"data\" chooses selection or deselection.", 0, { B_BOOL_TYPE }
},
{ "Selection", { B_SET_PROPERTY, 0 }, { B_DIRECT_SPECIFIER, 0 },
"Select or deselect all items in the selection. Boolean field \"data\" "
"chooses selection or deselection.", 0, { B_BOOL_TYPE }
},
{ 0 }
};
BListView::BListView(BRect frame, const char* name, list_view_type type,
uint32 resizingMode, uint32 flags)
:
BView(frame, name, resizingMode, flags | B_SCROLL_VIEW_AWARE)
{
_InitObject(type);
}
BListView::BListView(const char* name, list_view_type type, uint32 flags)
:
BView(name, flags | B_SCROLL_VIEW_AWARE)
{
_InitObject(type);
}
BListView::BListView(list_view_type type)
:
BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE
| B_SCROLL_VIEW_AWARE)
{
_InitObject(type);
}
BListView::BListView(BMessage* archive)
:
BView(archive)
{
int32 listType;
archive->FindInt32("_lv_type", &listType);
_InitObject((list_view_type)listType);
int32 i = 0;
BMessage subData;
while (archive->FindMessage("_l_items", i++, &subData) == B_OK) {
BArchivable* object = instantiate_object(&subData);
if (object == NULL)
continue;
BListItem* item = dynamic_cast<BListItem*>(object);
if (item != NULL)
AddItem(item);
}
if (archive->HasMessage("_msg")) {
BMessage* invokationMessage = new BMessage;
archive->FindMessage("_msg", invokationMessage);
SetInvocationMessage(invokationMessage);
}
if (archive->HasMessage("_2nd_msg")) {
BMessage* selectionMessage = new BMessage;
archive->FindMessage("_2nd_msg", selectionMessage);
SetSelectionMessage(selectionMessage);
}
}
BListView::~BListView()
{
delete fTrack;
SetSelectionMessage(NULL);
}
BArchivable*
BListView::Instantiate(BMessage* archive)
{
if (validate_instantiation(archive, "BListView"))
return new BListView(archive);
return NULL;
}
status_t
BListView::Archive(BMessage* data, bool deep) const
{
status_t status = BView::Archive(data, deep);
if (status < B_OK)
return status;
status = data->AddInt32("_lv_type", fListType);
if (status == B_OK && deep) {
BListItem* item;
int32 i = 0;
while ((item = ItemAt(i++)) != NULL) {
BMessage subData;
status = item->Archive(&subData, true);
if (status >= B_OK)
status = data->AddMessage("_l_items", &subData);
if (status < B_OK)
break;
}
}
if (status >= B_OK && InvocationMessage() != NULL)
status = data->AddMessage("_msg", InvocationMessage());
if (status == B_OK && fSelectMessage != NULL)
status = data->AddMessage("_2nd_msg", fSelectMessage);
return status;
}
void
BListView::Draw(BRect updateRect)
{
int32 count = CountItems();
if (count == 0)
return;
BRect itemFrame(0, 0, Bounds().right, -1);
for (int i = 0; i < count; i++) {
BListItem* item = ItemAt(i);
itemFrame.bottom = itemFrame.top + ceilf(item->Height()) - 1;
if (itemFrame.Intersects(updateRect))
DrawItem(item, itemFrame);
itemFrame.top = itemFrame.bottom + 1;
}
}
void
BListView::AttachedToWindow()
{
BView::AttachedToWindow();
_UpdateItems();
if (!Messenger().IsValid())
SetTarget(Window(), NULL);
_FixupScrollBar();
}
void
BListView::DetachedFromWindow()
{
BView::DetachedFromWindow();
}
void
BListView::AllAttached()
{
BView::AllAttached();
}
void
BListView::AllDetached()
{
BView::AllDetached();
}
void
BListView::FrameResized(float newWidth, float newHeight)
{
_FixupScrollBar();
_UpdateItems();
}
void
BListView::FrameMoved(BPoint newPosition)
{
BView::FrameMoved(newPosition);
}
void
BListView::TargetedByScrollView(BScrollView* view)
{
fScrollView = view;
}
void
BListView::WindowActivated(bool active)
{
BView::WindowActivated(active);
}
void
BListView::MessageReceived(BMessage* message)
{
if (message->HasSpecifiers()) {
BMessage reply(B_REPLY);
status_t err = B_BAD_SCRIPT_SYNTAX;
int32 index;
BMessage specifier;
int32 what;
const char* property;
if (message->GetCurrentSpecifier(&index, &specifier, &what, &property)
!= B_OK) {
return BView::MessageReceived(message);
}
BPropertyInfo propInfo(sProperties);
switch (propInfo.FindMatch(message, index, &specifier, what,
property)) {
case 0:
err = reply.AddInt32("result", CountItems());
break;
case 1: {
switch (what) {
case B_INDEX_SPECIFIER:
case B_REVERSE_INDEX_SPECIFIER: {
int32 index;
err = specifier.FindInt32("index", &index);
if (err >= B_OK) {
if (what == B_REVERSE_INDEX_SPECIFIER)
index = CountItems() - index;
if (index < 0 || index >= CountItems())
err = B_BAD_INDEX;
}
if (err >= B_OK) {
Select(index, false);
Invoke();
}
break;
}
case B_RANGE_SPECIFIER: {
case B_REVERSE_RANGE_SPECIFIER:
int32 beg, end, range;
err = specifier.FindInt32("index", &beg);
if (err >= B_OK)
err = specifier.FindInt32("range", &range);
if (err >= B_OK) {
if (what == B_REVERSE_RANGE_SPECIFIER)
beg = CountItems() - beg;
end = beg + range;
if (!(beg >= 0 && beg <= end && end < CountItems()))
err = B_BAD_INDEX;
if (err >= B_OK) {
if (fListType != B_MULTIPLE_SELECTION_LIST
&& end - beg > 1)
err = B_BAD_VALUE;
if (err >= B_OK) {
Select(beg, end - 1, false);
Invoke();
}
}
}
break;
}
}
break;
}
case 2: {
int32 count = 0;
for (int32 i = 0; i < CountItems(); i++) {
if (ItemAt(i)->IsSelected())
count++;
}
err = reply.AddInt32("result", count);
break;
}
case 3:
err = Invoke();
break;
case 4:
err = B_OK;
for (int32 i = 0; err >= B_OK && i < CountItems(); i++) {
if (ItemAt(i)->IsSelected())
err = reply.AddInt32("result", i);
}
break;
case 5: {
bool doSelect;
err = message->FindBool("data", &doSelect);
if (err >= B_OK) {
switch (what) {
case B_INDEX_SPECIFIER:
case B_REVERSE_INDEX_SPECIFIER: {
int32 index;
err = specifier.FindInt32("index", &index);
if (err >= B_OK) {
if (what == B_REVERSE_INDEX_SPECIFIER)
index = CountItems() - index;
if (index < 0 || index >= CountItems())
err = B_BAD_INDEX;
}
if (err >= B_OK) {
if (doSelect)
Select(index,
fListType == B_MULTIPLE_SELECTION_LIST);
else
Deselect(index);
}
break;
}
case B_RANGE_SPECIFIER: {
case B_REVERSE_RANGE_SPECIFIER:
int32 beg, end, range;
err = specifier.FindInt32("index", &beg);
if (err >= B_OK)
err = specifier.FindInt32("range", &range);
if (err >= B_OK) {
if (what == B_REVERSE_RANGE_SPECIFIER)
beg = CountItems() - beg;
end = beg + range;
if (!(beg >= 0 && beg <= end
&& end < CountItems()))
err = B_BAD_INDEX;
if (err >= B_OK) {
if (fListType != B_MULTIPLE_SELECTION_LIST
&& end - beg > 1)
err = B_BAD_VALUE;
if (doSelect)
Select(beg, end - 1, fListType
== B_MULTIPLE_SELECTION_LIST);
else {
for (int32 i = beg; i < end; i++)
Deselect(i);
}
}
}
break;
}
}
}
break;
}
case 6:
bool doSelect;
err = message->FindBool("data", &doSelect);
if (err >= B_OK) {
if (doSelect)
Select(0, CountItems() - 1, true);
else
DeselectAll();
}
break;
default:
return BView::MessageReceived(message);
}
if (err != B_OK) {
reply.what = B_MESSAGE_NOT_UNDERSTOOD;
reply.AddString("message", strerror(err));
}
reply.AddInt32("error", err);
message->SendReply(&reply);
return;
}
switch (message->what) {
case B_MOUSE_WHEEL_CHANGED:
if (!fTrack->is_dragging)
BView::MessageReceived(message);
break;
case B_SELECT_ALL:
if (fListType == B_MULTIPLE_SELECTION_LIST)
Select(0, CountItems() - 1, false);
break;
default:
BView::MessageReceived(message);
}
}
void
BListView::KeyDown(const char* bytes, int32 numBytes)
{
bool extend = fListType == B_MULTIPLE_SELECTION_LIST
&& (modifiers() & B_SHIFT_KEY) != 0;
if (fFirstSelected == -1
&& (bytes[0] == B_UP_ARROW || bytes[0] == B_DOWN_ARROW)) {
int32 lastItem = CountItems() - 1;
for (int32 i = 0; i <= lastItem; i++) {
if (ItemAt(i)->IsEnabled()) {
Select(i);
break;
}
}
return;
}
switch (bytes[0]) {
case B_UP_ARROW:
{
if (fAnchorIndex > 0) {
if (!extend || fAnchorIndex <= fFirstSelected) {
for (int32 i = 1; fAnchorIndex - i >= 0; i++) {
if (ItemAt(fAnchorIndex - i)->IsEnabled()) {
Select(fAnchorIndex - i, extend);
break;
}
}
} else {
Deselect(fAnchorIndex);
do
fAnchorIndex--;
while (fAnchorIndex > 0
&& !ItemAt(fAnchorIndex)->IsEnabled());
}
}
ScrollToSelection();
break;
}
case B_DOWN_ARROW:
{
int32 lastItem = CountItems() - 1;
if (fAnchorIndex < lastItem) {
if (!extend || fAnchorIndex >= fLastSelected) {
for (int32 i = 1; fAnchorIndex + i <= lastItem; i++) {
if (ItemAt(fAnchorIndex + i)->IsEnabled()) {
Select(fAnchorIndex + i, extend);
break;
}
}
} else {
Deselect(fAnchorIndex);
do
fAnchorIndex++;
while (fAnchorIndex < lastItem
&& !ItemAt(fAnchorIndex)->IsEnabled());
}
}
ScrollToSelection();
break;
}
case B_HOME:
if (extend) {
Select(0, fAnchorIndex, true);
fAnchorIndex = 0;
} else {
int32 lastItem = CountItems() - 1;
for (int32 i = 0; i <= lastItem; i++) {
if (ItemAt(i)->IsEnabled()) {
Select(i, false);
break;
}
}
}
ScrollToSelection();
break;
case B_END:
if (extend) {
Select(fAnchorIndex, CountItems() - 1, true);
fAnchorIndex = CountItems() - 1;
} else {
for (int32 i = CountItems() - 1; i >= 0; i--) {
if (ItemAt(i)->IsEnabled()) {
Select(i, false);
break;
}
}
}
ScrollToSelection();
break;
case B_PAGE_UP:
{
BPoint scrollOffset(LeftTop());
scrollOffset.y = std::max(0.0f, scrollOffset.y - Bounds().Height());
ScrollTo(scrollOffset);
break;
}
case B_PAGE_DOWN:
{
BPoint scrollOffset(LeftTop());
if (BListItem* item = LastItem()) {
scrollOffset.y += Bounds().Height();
scrollOffset.y = std::min(item->Bottom() - Bounds().Height(),
scrollOffset.y);
}
ScrollTo(scrollOffset);
break;
}
case B_RETURN:
case B_SPACE:
Invoke();
break;
default:
BView::KeyDown(bytes, numBytes);
}
}
void
BListView::MouseDown(BPoint where)
{
if (!IsFocus()) {
MakeFocus();
Sync();
Window()->UpdateIfNeeded();
}
int32 buttons = 0;
if (Window() != NULL) {
BMessage* currentMessage = Window()->CurrentMessage();
if (currentMessage != NULL)
currentMessage->FindInt32("buttons", &buttons);
}
int32 index = IndexOf(where);
BPoint delta = where - fTrack->drag_start;
bigtime_t sysTime;
Window()->CurrentMessage()->FindInt64("when", &sysTime);
bigtime_t timeDelta = sysTime - fTrack->last_click_time;
bigtime_t doubleClickSpeed;
get_click_speed(&doubleClickSpeed);
bool doubleClick = false;
if (timeDelta < doubleClickSpeed
&& fabs(delta.x) < kDoubleClickThreshold
&& fabs(delta.y) < kDoubleClickThreshold
&& fTrack->item_index == index) {
doubleClick = true;
}
if (doubleClick && index >= fFirstSelected && index <= fLastSelected) {
fTrack->drag_start.Set(INT32_MAX, INT32_MAX);
Invoke();
return BView::MouseDown(where);
}
if (!doubleClick) {
fTrack->drag_start = where;
fTrack->last_click_time = system_time();
fTrack->item_index = index;
fTrack->was_selected = index >= 0 ? ItemAt(index)->IsSelected() : false;
fTrack->try_drag = true;
SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
}
fTrack->buttons = buttons;
if (fTrack->buttons > 0 && fTrack->was_selected)
fTrack->selected_click_count++;
else
fTrack->selected_click_count = 0;
_DoSelection(index);
BView::MouseDown(where);
}
void
BListView::MouseUp(BPoint where)
{
bool wasDragging = fTrack->is_dragging;
fTrack->buttons = 0;
fTrack->try_drag = false;
fTrack->is_dragging = false;
if (fListType == B_MULTIPLE_SELECTION_LIST || wasDragging)
return BView::MouseUp(where);
int32 index = IndexOf(where);
if (index == fTrack->item_index)
return BView::MouseUp(where);
if (index == -1)
index = fTrack->item_index;
if (index == -1)
return BView::MouseUp(where);
BListItem* item = ItemAt(index);
if (item != NULL) {
item->Deselect();
_DoSelection(index);
}
BView::MouseUp(where);
}
void
BListView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
{
if (fTrack->item_index >= 0 && fTrack->try_drag) {
BPoint offset = where - fTrack->drag_start;
float dragDistance = sqrtf(offset.x * offset.x + offset.y * offset.y);
if (dragDistance >= 5.0f) {
fTrack->try_drag = false;
fTrack->is_dragging = InitiateDrag(fTrack->drag_start,
fTrack->item_index, fTrack->was_selected);
}
}
int32 index = IndexOf(where);
if (index == -1) {
if (where.y < Bounds().top)
index = 0;
else if (where.y > Bounds().bottom)
index = CountItems() - 1;
}
int32 lastIndex = fFirstSelected;
if (fTrack->buttons == 0 || index == -1)
return BView::MouseMoved(where, code, dragMessage);
if (where.x < Bounds().left || where.x > Bounds().right)
return BView::MouseMoved(where, code, dragMessage);
ScrollTo(index);
if (!fTrack->is_dragging && fListType != B_MULTIPLE_SELECTION_LIST
&& lastIndex != -1 && index != lastIndex) {
BListItem* last = ItemAt(lastIndex);
BListItem* item = ItemAt(index);
if (last != NULL && item != NULL) {
last->Deselect();
item->Select();
fFirstSelected = fLastSelected = index;
Invalidate(ItemFrame(lastIndex) | ItemFrame(index));
}
} else
Invalidate();
BView::MouseMoved(where, code, dragMessage);
}
bool
BListView::InitiateDrag(BPoint where, int32 index, bool wasSelected)
{
return false;
}
void
BListView::ResizeToPreferred()
{
BView::ResizeToPreferred();
}
void
BListView::GetPreferredSize(float *_width, float *_height)
{
int32 count = CountItems();
if (count > 0) {
float maxWidth = 0.0;
for (int32 i = 0; i < count; i++) {
float itemWidth = ItemAt(i)->Width();
if (itemWidth > maxWidth)
maxWidth = itemWidth;
}
if (_width != NULL)
*_width = maxWidth;
if (_height != NULL)
*_height = ItemAt(count - 1)->Bottom();
} else
BView::GetPreferredSize(_width, _height);
}
BSize
BListView::MinSize()
{
return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
}
BSize
BListView::MaxSize()
{
return BView::MaxSize();
}
BSize
BListView::PreferredSize()
{
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
}
void
BListView::MakeFocus(bool focused)
{
if (IsFocus() == focused)
return;
BView::MakeFocus(focused);
if (fScrollView)
fScrollView->SetBorderHighlighted(focused);
}
void
BListView::SetFont(const BFont* font, uint32 mask)
{
BView::SetFont(font, mask);
if (Window() != NULL && !Window()->InViewTransaction())
_UpdateItems();
}
void
BListView::ScrollTo(BPoint point)
{
BView::ScrollTo(point);
}
bool
BListView::AddItem(BListItem* item, int32 index)
{
if (!fList.AddItem(item, index))
return false;
if (fFirstSelected != -1 && index <= fFirstSelected)
fFirstSelected++;
if (fLastSelected != -1 && index <= fLastSelected)
fLastSelected++;
if (fAnchorIndex != -1 && index <= fAnchorIndex)
fAnchorIndex++;
if (Window()) {
BFont font;
GetFont(&font);
item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
item->Update(this, &font);
_RecalcItemTops(index + 1);
_FixupScrollBar();
_InvalidateFrom(index);
}
return true;
}
bool
BListView::AddItem(BListItem* item)
{
if (!fList.AddItem(item))
return false;
if (Window()) {
BFont font;
GetFont(&font);
int32 index = CountItems() - 1;
item->SetTop((index > 0) ? ItemAt(index - 1)->Bottom() + 1.0 : 0.0);
item->Update(this, &font);
_FixupScrollBar();
InvalidateItem(CountItems() - 1);
}
return true;
}
bool
BListView::AddList(BList* list, int32 index)
{
if (!fList.AddList(list, index))
return false;
int32 count = list->CountItems();
if (fFirstSelected != -1 && index < fFirstSelected)
fFirstSelected += count;
if (fLastSelected != -1 && index < fLastSelected)
fLastSelected += count;
if (fAnchorIndex != -1 && index < fAnchorIndex)
fAnchorIndex += count;
if (Window()) {
BFont font;
GetFont(&font);
for (int32 i = index; i <= (index + count - 1); i++) {
ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
ItemAt(i)->Update(this, &font);
}
_RecalcItemTops(index + count - 1);
_FixupScrollBar();
Invalidate();
}
return true;
}
bool
BListView::AddList(BList* list)
{
return AddList(list, CountItems());
}
BListItem*
BListView::RemoveItem(int32 index)
{
BListItem* item = ItemAt(index);
if (item == NULL)
return NULL;
if (item->IsSelected())
Deselect(index);
if (!fList.RemoveItem(item))
return NULL;
if (fFirstSelected != -1 && index < fFirstSelected)
fFirstSelected--;
if (fLastSelected != -1 && index < fLastSelected)
fLastSelected--;
if (fAnchorIndex != -1 && index < fAnchorIndex)
fAnchorIndex--;
_RecalcItemTops(index);
_InvalidateFrom(index);
_FixupScrollBar();
return item;
}
bool
BListView::RemoveItem(BListItem* item)
{
return BListView::RemoveItem(IndexOf(item)) != NULL;
}
bool
BListView::RemoveItems(int32 index, int32 count)
{
if (index >= fList.CountItems())
index = -1;
if (index < 0)
return false;
if (fAnchorIndex != -1 && index < fAnchorIndex)
fAnchorIndex = index;
fList.RemoveItems(index, count);
if (index < fList.CountItems())
_RecalcItemTops(index);
Invalidate();
return true;
}
void
BListView::SetSelectionMessage(BMessage* message)
{
delete fSelectMessage;
fSelectMessage = message;
}
void
BListView::SetInvocationMessage(BMessage* message)
{
BInvoker::SetMessage(message);
}
BMessage*
BListView::InvocationMessage() const
{
return BInvoker::Message();
}
uint32
BListView::InvocationCommand() const
{
return BInvoker::Command();
}
BMessage*
BListView::SelectionMessage() const
{
return fSelectMessage;
}
uint32
BListView::SelectionCommand() const
{
if (fSelectMessage)
return fSelectMessage->what;
return 0;
}
void
BListView::SetListType(list_view_type type)
{
if (fListType == B_MULTIPLE_SELECTION_LIST
&& type == B_SINGLE_SELECTION_LIST) {
Select(CurrentSelection(0));
}
fListType = type;
}
list_view_type
BListView::ListType() const
{
return fListType;
}
BListItem*
BListView::ItemAt(int32 index) const
{
return (BListItem*)fList.ItemAt(index);
}
int32
BListView::IndexOf(BListItem* item) const
{
if (Window()) {
if (item != NULL) {
int32 index = IndexOf(BPoint(0.0, item->Top()));
if (index >= 0 && fList.ItemAt(index) == item)
return index;
return -1;
}
}
return fList.IndexOf(item);
}
int32
BListView::IndexOf(BPoint point) const
{
int32 low = 0;
int32 high = fList.CountItems() - 1;
int32 mid = -1;
float frameTop = -1.0;
float frameBottom = 1.0;
while (high >= low) {
mid = (low + high) / 2;
frameTop = ItemAt(mid)->Top();
frameBottom = ItemAt(mid)->Bottom();
if (point.y < frameTop)
high = mid - 1;
else if (point.y > frameBottom)
low = mid + 1;
else
return mid;
}
return -1;
}
BListItem*
BListView::FirstItem() const
{
return (BListItem*)fList.FirstItem();
}
BListItem*
BListView::LastItem() const
{
return (BListItem*)fList.LastItem();
}
bool
BListView::HasItem(BListItem *item) const
{
return IndexOf(item) != -1;
}
int32
BListView::CountItems() const
{
return fList.CountItems();
}
void
BListView::MakeEmpty()
{
if (fList.IsEmpty())
return;
_DeselectAll(-1, -1);
fList.MakeEmpty();
if (Window()) {
_FixupScrollBar();
Invalidate();
}
}
bool
BListView::IsEmpty() const
{
return fList.IsEmpty();
}
void
BListView::DoForEach(bool (*func)(BListItem*))
{
fList.DoForEach(reinterpret_cast<bool (*)(void*)>(func));
}
void
BListView::DoForEach(bool (*func)(BListItem*, void*), void* arg)
{
fList.DoForEach(reinterpret_cast<bool (*)(void*, void*)>(func), arg);
}
const BListItem**
BListView::Items() const
{
return (const BListItem**)fList.Items();
}
void
BListView::InvalidateItem(int32 index)
{
Invalidate(ItemFrame(index));
}
void
BListView::ScrollTo(int32 index)
{
if (index < 0)
index = 0;
if (index > CountItems() - 1)
index = CountItems() - 1;
BRect itemFrame = ItemFrame(index);
BRect bounds = Bounds();
if (itemFrame.top < bounds.top)
BListView::ScrollTo(itemFrame.LeftTop());
else if (itemFrame.bottom > bounds.bottom)
BListView::ScrollTo(BPoint(0, itemFrame.bottom - bounds.Height()));
}
void
BListView::ScrollToSelection()
{
BRect itemFrame = ItemFrame(CurrentSelection(0));
if (itemFrame.top < Bounds().top
|| itemFrame.Height() > Bounds().Height())
ScrollBy(0, itemFrame.top - Bounds().top);
else if (itemFrame.bottom > Bounds().bottom)
ScrollBy(0, itemFrame.bottom - Bounds().bottom);
}
void
BListView::Select(int32 index, bool extend)
{
if (_Select(index, extend)) {
SelectionChanged();
InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
}
}
void
BListView::Select(int32 start, int32 finish, bool extend)
{
if (_Select(start, finish, extend)) {
SelectionChanged();
InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
}
}
bool
BListView::IsItemSelected(int32 index) const
{
BListItem* item = ItemAt(index);
if (item != NULL)
return item->IsSelected();
return false;
}
int32
BListView::CurrentSelection(int32 index) const
{
if (fFirstSelected == -1)
return -1;
if (index == 0)
return fFirstSelected;
for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
if (ItemAt(i)->IsSelected()) {
if (index == 0)
return i;
index--;
}
}
return -1;
}
status_t
BListView::Invoke(BMessage* message)
{
bool notify = false;
uint32 kind = InvokeKind(¬ify);
BMessage clone(kind);
status_t err = B_BAD_VALUE;
if (!message && !notify)
message = Message();
if (!message) {
if (!IsWatched())
return err;
} else
clone = *message;
clone.AddInt64("when", (int64)system_time());
clone.AddPointer("source", this);
clone.AddMessenger("be:sender", BMessenger(this));
if (fListType == B_SINGLE_SELECTION_LIST)
clone.AddInt32("index", fFirstSelected);
else {
if (fFirstSelected >= 0) {
for (int32 i = fFirstSelected; i <= fLastSelected; i++) {
if (ItemAt(i)->IsSelected())
clone.AddInt32("index", i);
}
}
}
if (message)
err = BInvoker::Invoke(&clone);
SendNotices(kind, &clone);
return err;
}
void
BListView::DeselectAll()
{
if (_DeselectAll(-1, -1)) {
SelectionChanged();
InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
}
}
void
BListView::DeselectExcept(int32 exceptFrom, int32 exceptTo)
{
if (exceptFrom > exceptTo || exceptFrom < 0 || exceptTo < 0)
return;
if (_DeselectAll(exceptFrom, exceptTo)) {
SelectionChanged();
InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
}
}
void
BListView::Deselect(int32 index)
{
if (_Deselect(index)) {
SelectionChanged();
InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
}
}
void
BListView::SelectionChanged()
{
}
void
BListView::SortItems(int (*cmp)(const void *, const void *))
{
if (_DeselectAll(-1, -1)) {
SelectionChanged();
InvokeNotify(fSelectMessage, B_CONTROL_MODIFIED);
}
fList.SortItems(cmp);
_RecalcItemTops(0);
Invalidate();
}
bool
BListView::SwapItems(int32 a, int32 b)
{
MiscData data;
data.swap.a = a;
data.swap.b = b;
return DoMiscellaneous(B_SWAP_OP, &data);
}
bool
BListView::MoveItem(int32 from, int32 to)
{
MiscData data;
data.move.from = from;
data.move.to = to;
return DoMiscellaneous(B_MOVE_OP, &data);
}
bool
BListView::ReplaceItem(int32 index, BListItem* item)
{
MiscData data;
data.replace.index = index;
data.replace.item = item;
return DoMiscellaneous(B_REPLACE_OP, &data);
}
BRect
BListView::ItemFrame(int32 index)
{
BRect frame = Bounds();
if (index < 0 || index >= CountItems()) {
frame.top = 0;
frame.bottom = -1;
} else {
BListItem* item = ItemAt(index);
frame.top = item->Top();
frame.bottom = item->Bottom();
}
return frame;
}
BHandler*
BListView::ResolveSpecifier(BMessage* message, int32 index,
BMessage* specifier, int32 what, const char* property)
{
BPropertyInfo propInfo(sProperties);
if (propInfo.FindMatch(message, 0, specifier, what, property) < 0) {
return BView::ResolveSpecifier(message, index, specifier, what,
property);
}
return this;
}
status_t
BListView::GetSupportedSuites(BMessage* data)
{
if (data == NULL)
return B_BAD_VALUE;
status_t err = data->AddString("suites", "suite/vnd.Be-list-view");
BPropertyInfo propertyInfo(sProperties);
if (err == B_OK)
err = data->AddFlat("messages", &propertyInfo);
if (err == B_OK)
return BView::GetSupportedSuites(data);
return err;
}
status_t
BListView::Perform(perform_code code, void* _data)
{
switch (code) {
case PERFORM_CODE_MIN_SIZE:
((perform_data_min_size*)_data)->return_value
= BListView::MinSize();
return B_OK;
case PERFORM_CODE_MAX_SIZE:
((perform_data_max_size*)_data)->return_value
= BListView::MaxSize();
return B_OK;
case PERFORM_CODE_PREFERRED_SIZE:
((perform_data_preferred_size*)_data)->return_value
= BListView::PreferredSize();
return B_OK;
case PERFORM_CODE_LAYOUT_ALIGNMENT:
((perform_data_layout_alignment*)_data)->return_value
= BListView::LayoutAlignment();
return B_OK;
case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
((perform_data_has_height_for_width*)_data)->return_value
= BListView::HasHeightForWidth();
return B_OK;
case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
{
perform_data_get_height_for_width* data
= (perform_data_get_height_for_width*)_data;
BListView::GetHeightForWidth(data->width, &data->min, &data->max,
&data->preferred);
return B_OK;
}
case PERFORM_CODE_SET_LAYOUT:
{
perform_data_set_layout* data = (perform_data_set_layout*)_data;
BListView::SetLayout(data->layout);
return B_OK;
}
case PERFORM_CODE_LAYOUT_INVALIDATED:
{
perform_data_layout_invalidated* data
= (perform_data_layout_invalidated*)_data;
BListView::LayoutInvalidated(data->descendants);
return B_OK;
}
case PERFORM_CODE_DO_LAYOUT:
{
BListView::DoLayout();
return B_OK;
}
}
return BView::Perform(code, _data);
}
bool
BListView::DoMiscellaneous(MiscCode code, MiscData* data)
{
if (code > B_SWAP_OP)
return false;
switch (code) {
case B_NO_OP:
break;
case B_REPLACE_OP:
return _ReplaceItem(data->replace.index, data->replace.item);
case B_MOVE_OP:
return _MoveItem(data->move.from, data->move.to);
case B_SWAP_OP:
return _SwapItems(data->swap.a, data->swap.b);
}
return false;
}
void BListView::_ReservedListView2() {}
void BListView::_ReservedListView3() {}
void BListView::_ReservedListView4() {}
BListView&
BListView::operator=(const BListView& )
{
return *this;
}
void
BListView::_InitObject(list_view_type type)
{
fListType = type;
fFirstSelected = -1;
fLastSelected = -1;
fAnchorIndex = -1;
fSelectMessage = NULL;
fScrollView = NULL;
fTrack = new track_data;
fTrack->drag_start = B_ORIGIN;
fTrack->item_index = -1;
fTrack->buttons = 0;
fTrack->selected_click_count = 0;
fTrack->was_selected = false;
fTrack->try_drag = false;
fTrack->is_dragging = false;
fTrack->last_click_time = 0;
SetViewUIColor(B_LIST_BACKGROUND_COLOR);
SetLowUIColor(B_LIST_BACKGROUND_COLOR);
}
void
BListView::_FixupScrollBar()
{
BScrollBar* vertScroller = ScrollBar(B_VERTICAL);
if (vertScroller != NULL) {
BRect bounds = Bounds();
int32 count = CountItems();
float itemHeight = 0.0;
if (CountItems() > 0)
itemHeight = ItemAt(CountItems() - 1)->Bottom();
if (bounds.Height() > itemHeight) {
vertScroller->SetRange(0.0, 0.0);
vertScroller->SetValue(0.0);
} else {
vertScroller->SetRange(0.0, itemHeight - bounds.Height() - 1.0);
vertScroller->SetProportion(bounds.Height () / itemHeight);
if (itemHeight < bounds.bottom)
ScrollBy(0.0, bounds.bottom - itemHeight);
}
if (count != 0)
vertScroller->SetSteps(
ceilf(FirstItem()->Height()), bounds.Height());
}
BScrollBar* horizontalScroller = ScrollBar(B_HORIZONTAL);
if (horizontalScroller != NULL) {
float w;
GetPreferredSize(&w, NULL);
BRect scrollBarSize = horizontalScroller->Bounds();
if (w <= scrollBarSize.Width()) {
horizontalScroller->SetRange(0.0, 0.0);
horizontalScroller->SetValue(0.0);
} else {
horizontalScroller->SetRange(0, w - scrollBarSize.Width());
horizontalScroller->SetProportion(scrollBarSize.Width() / w);
}
}
}
void
BListView::_InvalidateFrom(int32 index)
{
int32 count = CountItems();
if (index >= count)
index = count;
index--;
BRect dirty = Bounds();
if (index >= 0)
dirty.top = ItemFrame(index).bottom + 1;
Invalidate(dirty);
}
void
BListView::_UpdateItems()
{
BFont font;
GetFont(&font);
for (int32 i = 0; i < CountItems(); i++) {
ItemAt(i)->SetTop((i > 0) ? ItemAt(i - 1)->Bottom() + 1.0 : 0.0);
ItemAt(i)->Update(this, &font);
}
}
bool
BListView::_Select(int32 index, bool extend)
{
if (index < 0 || index >= CountItems())
return false;
BAutolock locker(Window());
if (Window() != NULL && !locker.IsLocked())
return false;
bool changed = false;
if (!extend && fFirstSelected != -1)
changed = _DeselectAll(index, index);
fAnchorIndex = index;
BListItem* item = ItemAt(index);
if (!item->IsEnabled() || item->IsSelected()) {
return changed;
}
if (fFirstSelected == -1) {
fFirstSelected = index;
fLastSelected = index;
} else if (index < fFirstSelected) {
fFirstSelected = index;
} else if (index > fLastSelected) {
fLastSelected = index;
}
item->Select();
if (Window() != NULL)
InvalidateItem(index);
return true;
}
bool
BListView::_Select(int32 from, int32 to, bool extend)
{
if (to < from)
return false;
BAutolock locker(Window());
if (Window() && !locker.IsLocked())
return false;
bool changed = false;
if (fFirstSelected != -1 && !extend)
changed = _DeselectAll(from, to);
if (fFirstSelected == -1) {
fFirstSelected = from;
fLastSelected = to;
} else {
if (from < fFirstSelected)
fFirstSelected = from;
if (to > fLastSelected)
fLastSelected = to;
}
for (int32 i = from; i <= to; ++i) {
BListItem* item = ItemAt(i);
if (item != NULL && !item->IsSelected() && item->IsEnabled()) {
item->Select();
if (Window() != NULL)
InvalidateItem(i);
changed = true;
}
}
return changed;
}
bool
BListView::_Deselect(int32 index)
{
if (index < 0 || index >= CountItems())
return false;
BWindow* window = Window();
BAutolock locker(window);
if (window != NULL && !locker.IsLocked())
return false;
BListItem* item = ItemAt(index);
if (item != NULL && item->IsSelected()) {
BRect frame(ItemFrame(index));
BRect bounds(Bounds());
item->Deselect();
if (fFirstSelected == index && fLastSelected == index) {
fFirstSelected = -1;
fLastSelected = -1;
} else {
if (fFirstSelected == index)
fFirstSelected = _CalcFirstSelected(index);
if (fLastSelected == index)
fLastSelected = _CalcLastSelected(index);
}
if (window && bounds.Intersects(frame))
DrawItem(ItemAt(index), frame, true);
}
return true;
}
bool
BListView::_DeselectAll(int32 exceptFrom, int32 exceptTo)
{
if (fFirstSelected == -1)
return false;
BAutolock locker(Window());
if (Window() && !locker.IsLocked())
return false;
bool changed = false;
for (int32 index = fFirstSelected; index <= fLastSelected; index++) {
if (exceptFrom != -1 && exceptFrom <= index && exceptTo >= index)
continue;
BListItem* item = ItemAt(index);
if (item != NULL && item->IsSelected()) {
item->Deselect();
InvalidateItem(index);
changed = true;
}
}
if (!changed)
return false;
if (exceptFrom != -1) {
fFirstSelected = _CalcFirstSelected(exceptFrom);
fLastSelected = _CalcLastSelected(exceptTo);
} else
fFirstSelected = fLastSelected = -1;
return true;
}
int32
BListView::_CalcFirstSelected(int32 after)
{
if (after >= CountItems())
return -1;
int32 count = CountItems();
for (int32 i = after; i < count; i++) {
if (ItemAt(i)->IsSelected())
return i;
}
return -1;
}
int32
BListView::_CalcLastSelected(int32 before)
{
if (before < 0)
return -1;
before = std::min(CountItems() - 1, before);
for (int32 i = before; i >= 0; i--) {
if (ItemAt(i)->IsSelected())
return i;
}
return -1;
}
void
BListView::DrawItem(BListItem* item, BRect itemRect, bool complete)
{
if (!item->IsEnabled()) {
rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
rgb_color disabledColor;
if (textColor.red + textColor.green + textColor.blue > 128 * 3)
disabledColor = tint_color(textColor, B_DARKEN_2_TINT);
else
disabledColor = tint_color(textColor, B_LIGHTEN_2_TINT);
SetHighColor(disabledColor);
} else if (item->IsSelected())
SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
else
SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
item->DrawItem(this, itemRect, complete);
}
bool
BListView::_SwapItems(int32 a, int32 b)
{
BRect aFrame = ItemFrame(a);
BRect bFrame = ItemFrame(b);
if (!fList.SwapItems(a, b))
return false;
if (a == b) {
return true;
}
if (fAnchorIndex == a)
fAnchorIndex = b;
else if (fAnchorIndex == b)
fAnchorIndex = a;
int32 first = std::min(a, b);
int32 last = std::max(a, b);
if (ItemAt(a)->IsSelected() != ItemAt(b)->IsSelected()) {
if (first < fFirstSelected || last > fLastSelected) {
_RescanSelection(std::min(first, fFirstSelected),
std::max(last, fLastSelected));
}
SelectionChanged();
}
ItemAt(a)->SetTop(aFrame.top);
ItemAt(b)->SetTop(bFrame.top);
if (Window()) {
if (aFrame.Height() != bFrame.Height()) {
_RecalcItemTops(first, last);
Invalidate(aFrame | bFrame);
} else {
Invalidate(aFrame);
Invalidate(bFrame);
}
}
return true;
}
bool
BListView::_MoveItem(int32 from, int32 to)
{
BRect frameFrom = ItemFrame(from);
BRect frameTo = ItemFrame(to);
if (!fList.MoveItem(from, to))
return false;
if (fAnchorIndex == from)
fAnchorIndex = to;
if (ItemAt(to)->IsSelected()) {
_RescanSelection(from, to);
SelectionChanged();
}
_RecalcItemTops((to > from) ? from : to);
if (Window()) {
Invalidate(frameFrom | frameTo);
}
return true;
}
bool
BListView::_ReplaceItem(int32 index, BListItem* item)
{
if (item == NULL)
return false;
BListItem* old = ItemAt(index);
if (!old)
return false;
BRect frame = ItemFrame(index);
bool selectionChanged = old->IsSelected() != item->IsSelected();
if (!fList.ReplaceItem(index, item))
return false;
if (selectionChanged) {
int32 start = std::min(fFirstSelected, index);
int32 end = std::max(fLastSelected, index);
_RescanSelection(start, end);
SelectionChanged();
}
_RecalcItemTops(index);
bool itemHeightChanged = frame != ItemFrame(index);
if (Window()) {
if (itemHeightChanged)
_InvalidateFrom(index);
else
Invalidate(frame);
}
if (itemHeightChanged)
_FixupScrollBar();
return true;
}
void
BListView::_RescanSelection(int32 from, int32 to)
{
if (from > to) {
int32 tmp = from;
from = to;
to = tmp;
}
from = std::max((int32)0, from);
to = std::min(to, CountItems() - 1);
if (fAnchorIndex != -1) {
if (fAnchorIndex == from)
fAnchorIndex = to;
else if (fAnchorIndex == to)
fAnchorIndex = from;
}
for (int32 i = from; i <= to; i++) {
if (ItemAt(i)->IsSelected()) {
fFirstSelected = i;
break;
}
}
if (fFirstSelected > from)
from = fFirstSelected;
fLastSelected = fFirstSelected;
for (int32 i = from; i <= to; i++) {
if (ItemAt(i)->IsSelected())
fLastSelected = i;
}
}
void
BListView::_RecalcItemTops(int32 start, int32 end)
{
int32 count = CountItems();
if ((start < 0) || (start >= count))
return;
if (end >= 0)
count = end + 1;
float top = (start == 0) ? 0.0 : ItemAt(start - 1)->Bottom() + 1.0;
for (int32 i = start; i < count; i++) {
BListItem *item = ItemAt(i);
item->SetTop(top);
top += ceilf(item->Height());
}
}
void
BListView::_DoSelection(int32 index)
{
BListItem* item = ItemAt(index);
if (index < 0 || item == NULL)
return;
if (!item->IsEnabled())
return DeselectAll();
if (fListType == B_MULTIPLE_SELECTION_LIST) {
if ((modifiers() & B_SHIFT_KEY) != 0) {
if (index >= fFirstSelected && index < fLastSelected) {
DeselectExcept(fFirstSelected, index);
} else {
Select(std::min(index, fFirstSelected),
std::max(index, fLastSelected));
}
} else if ((modifiers() & B_COMMAND_KEY) != 0) {
if (item->IsSelected())
Deselect(index);
else
Select(index, true);
} else if (fTrack->selected_click_count != 1)
Select(index);
} else {
if ((modifiers() & B_COMMAND_KEY) != 0 && item->IsSelected())
Deselect(index);
else
Select(index);
}
}