* Copyright 2006-2007, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include <stdio.h>
#include <malloc.h>
#include <new>
#include <Bitmap.h>
#include <Cursor.h>
#include <Entry.h>
#include <MessageRunner.h>
#include <Messenger.h>
#include <ScrollBar.h>
#include <ScrollView.h>
#include <String.h>
#include <Window.h>
#include "ListViews.h"
const unsigned char kCopyCursor[] = { 16, 1, 1, 1,
0x00, 0x00, 0x70, 0x00, 0x48, 0x00, 0x48, 0x00,
0x27, 0xc0, 0x24, 0xb8, 0x12, 0x54, 0x10, 0x02,
0x79, 0xe2, 0x99, 0x22, 0x85, 0x7a, 0x61, 0x4a,
0x19, 0xca, 0x04, 0x4a, 0x02, 0x78, 0x00, 0x00,
0x00, 0x00, 0x70, 0x00, 0x78, 0x00, 0x78, 0x00,
0x3f, 0xc0, 0x3f, 0xf8, 0x1f, 0xfc, 0x1f, 0xfe,
0x7f, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfe,
0x1f, 0xfe, 0x07, 0xfe, 0x03, 0xf8, 0x00, 0x00 };
#define MAX_DRAG_HEIGHT 200.0
#define ALPHA 170
#define TEXT_OFFSET 5.0
enum {
MSG_TICK = 'tick',
};
using std::nothrow;
SimpleItem::SimpleItem( const char *name )
: BStringItem( name )
{
}
SimpleItem::~SimpleItem()
{
}
void
SimpleItem::Draw(BView *owner, BRect frame, uint32 flags)
{
DrawBackground(owner, frame, flags);
if (IsSelected())
owner->SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
else
owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
font_height fh;
owner->GetFontHeight( &fh );
const char* text = Text();
BString truncatedString( text );
owner->TruncateString( &truncatedString, B_TRUNCATE_MIDDLE,
frame.Width() - TEXT_OFFSET - 4.0 );
float height = frame.Height();
float textHeight = fh.ascent + fh.descent;
BPoint textPoint;
textPoint.x = frame.left + TEXT_OFFSET;
textPoint.y = frame.top
+ ceilf(height / 2.0 - textHeight / 2.0
+ fh.ascent);
owner->DrawString(truncatedString.String(), textPoint);
}
void
SimpleItem::DrawBackground(BView *owner, BRect frame, uint32 flags)
{
if (flags & FLAGS_FOCUSED) {
owner->SetLowColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
owner->StrokeRect(frame, B_SOLID_LOW);
frame.InsetBy(1.0, 1.0);
}
rgb_color color = ui_color(B_LIST_BACKGROUND_COLOR);
if (IsSelected())
color = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
if ( flags & FLAGS_TINTED_LINE )
color = tint_color(color, 1.06);
owner->SetLowColor(color);
owner->FillRect(frame, B_SOLID_LOW);
}
DragSortableListView::DragSortableListView(BRect frame, const char* name,
list_view_type type, uint32 resizingMode,
uint32 flags)
: BListView(frame, name, type, resizingMode, flags),
fDropRect(0.0, 0.0, -1.0, -1.0),
fScrollPulse(NULL),
fDropIndex(-1),
fLastClickedItem(NULL),
fScrollView(NULL),
fDragCommand(B_SIMPLE_DATA),
fFocusedIndex(-1)
{
SetViewColor(B_TRANSPARENT_32_BIT);
}
DragSortableListView::~DragSortableListView()
{
delete fScrollPulse;
}
void
DragSortableListView::AttachedToWindow()
{
BListView::AttachedToWindow();
BRect bounds = Bounds();
BListView::FrameResized(bounds.Width(), bounds.Height());
}
void
DragSortableListView::DetachedFromWindow()
{
}
void
DragSortableListView::FrameResized(float width, float height)
{
BListView::FrameResized(width, height);
Invalidate();
}
// MakeFocus
void
DragSortableListView::MakeFocus(bool focused)
{
if (focused != IsFocus()) {
Invalidate();
BListView::MakeFocus(focused);
}
}
*/
void
DragSortableListView::Draw( BRect updateRect )
{
int32 firstIndex = IndexOf(updateRect.LeftTop());
int32 lastIndex = IndexOf(updateRect.RightBottom());
if (firstIndex >= 0) {
if (lastIndex < firstIndex)
lastIndex = CountItems() - 1;
BRect r = updateRect;
for (int32 i = firstIndex; i <= lastIndex; i++) {
r = ItemFrame(i);
DrawListItem(this, i, r);
}
updateRect.top = r.bottom + 1.0;
if (updateRect.IsValid()) {
SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR));
FillRect(updateRect, B_SOLID_LOW);
}
} else {
SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR));
FillRect(updateRect, B_SOLID_LOW);
}
if (fDropRect.IsValid()) {
SetHighColor(255, 0, 0, 255);
StrokeRect(fDropRect);
}
if (IsFocus()) {
SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
StrokeRect(Bounds());
}*/
}
void
DragSortableListView::ScrollTo(BPoint where)
{
uint32 buttons;
BPoint point;
GetMouse(&point, &buttons, false);
uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
MouseMoved(point, transit, &fDragMessageCopy);
BListView::ScrollTo(where);
}
void
DragSortableListView::TargetedByScrollView(BScrollView* scrollView)
{
fScrollView = scrollView;
BListView::TargetedByScrollView(scrollView);
}
bool
DragSortableListView::InitiateDrag( BPoint point, int32 index, bool )
{
if (fFocusedIndex >= 0)
return false;
bool success = false;
BListItem* item = ItemAt( CurrentSelection( 0 ) );
if ( !item ) {
Select( index );
item = ItemAt( index );
}
if ( item ) {
BMessage msg( fDragCommand );
MakeDragMessage( &msg );
float width = Bounds().Width();
BRect dragRect(0.0, 0.0, width, -1.0);
int32 numItems;
bool fade = false;
for (numItems = 0; BListItem* item = ItemAt( CurrentSelection( numItems ) ); numItems++) {
dragRect.bottom += ceilf( item->Height() ) + 1.0;
if ( dragRect.Height() > MAX_DRAG_HEIGHT ) {
fade = true;
dragRect.bottom = MAX_DRAG_HEIGHT;
numItems++;
break;
}
}
BBitmap* dragBitmap = new BBitmap( dragRect, B_RGB32, true );
if ( dragBitmap && dragBitmap->IsValid() ) {
if ( BView *v = new BView( dragBitmap->Bounds(), "helper", B_FOLLOW_NONE, B_WILL_DRAW ) ) {
dragBitmap->AddChild( v );
dragBitmap->Lock();
BRect itemBounds( dragRect) ;
itemBounds.bottom = 0.0;
for ( int32 i = 0; i < numItems; i++ ) {
int32 index = CurrentSelection( i );
BListItem* item = ItemAt( index );
itemBounds.bottom = itemBounds.top + ceilf( item->Height() );
if ( itemBounds.bottom > dragRect.bottom )
itemBounds.bottom = dragRect.bottom;
DrawListItem( v, index, itemBounds );
itemBounds.top = itemBounds.bottom + 1.0;
}
v->SetHighColor( 0, 0, 0, 255 );
v->StrokeRect( v->Bounds() );
v->Sync();
uint8 *bits = (uint8 *)dragBitmap->Bits();
int32 height = (int32)dragBitmap->Bounds().Height() + 1;
int32 width = (int32)dragBitmap->Bounds().Width() + 1;
int32 bpr = dragBitmap->BytesPerRow();
if (fade) {
for ( int32 y = 0; y < height - ALPHA / 2; y++, bits += bpr ) {
uint8 *line = bits + 3;
for (uint8 *end = line + 4 * width; line < end; line += 4)
*line = ALPHA;
}
for ( int32 y = height - ALPHA / 2; y < height; y++, bits += bpr ) {
uint8 *line = bits + 3;
for (uint8 *end = line + 4 * width; line < end; line += 4)
*line = (height - y) << 1;
}
} else {
for ( int32 y = 0; y < height; y++, bits += bpr ) {
uint8 *line = bits + 3;
for (uint8 *end = line + 4 * width; line < end; line += 4)
*line = ALPHA;
}
}
dragBitmap->Unlock();
}
} else {
delete dragBitmap;
dragBitmap = NULL;
}
if (dragBitmap)
DragMessage( &msg, dragBitmap, B_OP_ALPHA, BPoint( 0.0, 0.0 ) );
else
DragMessage( &msg, dragRect.OffsetToCopy( point ), this );
_SetDragMessage(&msg);
success = true;
}
return success;
}
void
DragSortableListView::WindowActivated( bool active )
{
if ( BView* view = Parent() )
view->Invalidate();
}
void
DragSortableListView::MessageReceived(BMessage* message)
{
if (AcceptDragMessage(message)) {
DragSortableListView *list = NULL;
if (message->FindPointer("list", (void **)&list) == B_OK
&& list == this) {
int32 count = CountItems();
if (fDropIndex < 0 || fDropIndex > count)
fDropIndex = count;
BList indices;
int32 index;
for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; i++)
indices.AddItem((void*)(addr_t)index);
if (indices.CountItems() > 0) {
if (modifiers() & B_SHIFT_KEY)
CopyItems(indices, fDropIndex);
else
MoveItems(indices, fDropIndex);
}
fDropIndex = -1;
}
} else {
switch (message->what) {
case MSG_TICK: {
float scrollV = 0.0;
BRect rect(Bounds());
BPoint point;
uint32 buttons;
GetMouse(&point, &buttons, false);
if (rect.Contains(point)) {
float hotDist = rect.Height() * SCROLL_AREA;
if (point.y > rect.bottom - hotDist)
scrollV = hotDist - (rect.bottom - point.y);
else if (point.y < rect.top + hotDist)
scrollV = (point.y - rect.top) - hotDist;
}
if (scrollV != 0.0 && fScrollView) {
if (BScrollBar* scrollBar = fScrollView->ScrollBar(B_VERTICAL)) {
float value = scrollBar->Value();
scrollBar->SetValue(scrollBar->Value() + scrollV);
if (scrollBar->Value() != value) {
uint32 buttons;
BPoint point;
GetMouse(&point, &buttons, false);
uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
MouseMoved(point, transit, &fDragMessageCopy);
}
}
}
break;
}
case B_MODIFIERS_CHANGED:
ModifiersChanged();
break;
case B_MOUSE_WHEEL_CHANGED: {
BListView::MessageReceived( message );
BPoint point;
uint32 buttons;
GetMouse(&point, &buttons, false);
uint32 transit = Bounds().Contains(point) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
MouseMoved(point, transit, &fDragMessageCopy);
break;
}
default:
BListView::MessageReceived( message );
break;
}
}
}
void
DragSortableListView::KeyDown( const char* bytes, int32 numBytes )
{
if (numBytes < 1)
return;
if (bytes[0] == B_BACKSPACE)
RemoveSelected();
BListView::KeyDown(bytes, numBytes);
}
void
DragSortableListView::MouseDown( BPoint where )
{
int32 clicks = 1;
uint32 buttons = 0;
Window()->CurrentMessage()->FindInt32("clicks", &clicks);
Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
int32 clickedIndex = -1;
for (int32 i = 0; BListItem* item = ItemAt(i); i++) {
if (ItemFrame(i).Contains(where)) {
if (clicks == 2) {
if (fLastClickedItem == item)
DoubleClicked(i);
} else {
fLastClickedItem = item;
}
clickedIndex = i;
break;
}
}
if (clickedIndex == -1)
fLastClickedItem = NULL;
BListItem* item = ItemAt(clickedIndex);
if (ListType() == B_MULTIPLE_SELECTION_LIST
&& item && (buttons & B_SECONDARY_MOUSE_BUTTON)) {
if (item->IsSelected())
Deselect(clickedIndex);
else
Select(clickedIndex, true);
} else {
BListView::MouseDown(where);
}
}
void
DragSortableListView::MouseMoved(BPoint where, uint32 transit, const BMessage *msg)
{
if (msg && AcceptDragMessage(msg)) {
switch (transit) {
case B_ENTERED_VIEW:
case B_INSIDE_VIEW: {
_SetDragMessage(msg);
SetDropTargetRect(msg, where);
BRect r = Bounds();
r.InsetBy(0.0, r.Height() * SCROLL_AREA);
SetAutoScrolling(!r.Contains(where));
break;
}
case B_EXITED_VIEW:
_SetDragMessage(NULL);
SetAutoScrolling(false);
case B_OUTSIDE_VIEW:
_RemoveDropAnticipationRect();
break;
}
} else {
_RemoveDropAnticipationRect();
BListView::MouseMoved(where, transit, msg);
_SetDragMessage(NULL);
SetAutoScrolling(false);
BCursor cursor(B_HAND_CURSOR);
SetViewCursor(&cursor, true);
}
fLastMousePos = where;
}
void
DragSortableListView::MouseUp( BPoint where )
{
_SetDropAnticipationRect( BRect( 0.0, 0.0, -1.0, -1.0 ) );
SetAutoScrolling(false);
_SetDragMessage(NULL);
BListView::MouseUp( where );
BCursor cursor(B_HAND_CURSOR);
SetViewCursor(&cursor, true);
}
void
DragSortableListView::DrawItem( BListItem *item, BRect itemFrame, bool complete )
{
DrawListItem( this, IndexOf( item ), itemFrame );
SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
StrokeRect(Bounds());
}*/
}
bool
DragSortableListView::MouseWheelChanged(float x, float y)
{
BPoint where;
uint32 buttons;
GetMouse(&where, &buttons, false);
if (Bounds().Contains(where))
return true;
else
return false;
}
void
DragSortableListView::SetDragCommand(uint32 command)
{
fDragCommand = command;
}
void
DragSortableListView::ModifiersChanged()
{
SetDropTargetRect(&fDragMessageCopy, fLastMousePos);
}
void
DragSortableListView::SetItemFocused(int32 index)
{
InvalidateItem(fFocusedIndex);
InvalidateItem(index);
fFocusedIndex = index;
}
bool
DragSortableListView::AcceptDragMessage(const BMessage* message) const
{
return message->what == fDragCommand;
}
void
DragSortableListView::SetDropTargetRect(const BMessage* message, BPoint where)
{
if (AcceptDragMessage(message)) {
bool copy = modifiers() & B_SHIFT_KEY;
bool replaceAll = !message->HasPointer("list") && copy;
BRect r = Bounds();
if (replaceAll) {
r.bottom--;
_SetDropAnticipationRect(r);
fDropIndex = -1;
} else {
r = ItemFrame(0);
where.y += r.Height() / 2.0;
int32 index = IndexOf(where);
if (index < 0)
index = CountItems();
_SetDropIndex(index);
const uchar* cursorData = copy ? kCopyCursor : B_HAND_CURSOR;
BCursor cursor(cursorData);
SetViewCursor(&cursor, true);
}
}
}
void
DragSortableListView::SetAutoScrolling(bool enable)
{
if (fScrollPulse && enable)
return;
if (enable) {
BMessenger messenger(this, Window());
BMessage message(MSG_TICK);
fScrollPulse = new BMessageRunner(messenger, &message, 40000LL);
} else {
delete fScrollPulse;
fScrollPulse = NULL;
}
}
bool
DragSortableListView::DoesAutoScrolling() const
{
return fScrollPulse;
}
void
DragSortableListView::ScrollTo(int32 index)
{
if (index < 0)
index = 0;
if (index >= CountItems())
index = CountItems() - 1;
if (ItemAt(index)) {
BRect itemFrame = ItemFrame(index);
BRect bounds = Bounds();
if (itemFrame.top < bounds.top) {
ScrollTo(itemFrame.LeftTop());
} else if (itemFrame.bottom > bounds.bottom) {
ScrollTo(BPoint(0.0, itemFrame.bottom - bounds.Height()));
}
}
}
void
DragSortableListView::MoveItems(const BList& indices, int32 index)
{
DeselectAll();
BList removedItems;
int32 count = indices.CountItems();
for (int32 i = 0; i < count; i++) {
int32 removeIndex = (int32)(addr_t)indices.ItemAtFast(i) - i;
BListItem* item = RemoveItem(removeIndex);
if (item && removedItems.AddItem((void*)item)) {
if (removeIndex < index)
index--;
}
}
count = removedItems.CountItems();
for (int32 i = 0; i < count; i++) {
BListItem* item = (BListItem*)removedItems.ItemAtFast(i);
if (AddItem(item, index)) {
Select(index, true);
index++;
} else
delete item;
}
}
void
DragSortableListView::CopyItems(const BList& indices, int32 toIndex)
{
DeselectAll();
BList clonedItems;
int32 count = indices.CountItems();
for (int32 i = 0; i < count; i++) {
int32 index = (int32)(addr_t)indices.ItemAtFast(i);
BListItem* item = CloneItem(index);
if (item && !clonedItems.AddItem((void*)(addr_t)item))
delete item;
}
count = clonedItems.CountItems();
for (int32 i = 0; i < count; i++) {
BListItem* item = (BListItem*)clonedItems.ItemAtFast(i);
if (AddItem(item, toIndex)) {
Select(toIndex, true);
toIndex++;
} else
delete item;
}
}
void
DragSortableListView::RemoveItemList(const BList& indices)
{
int32 count = indices.CountItems();
for (int32 i = 0; i < count; i++) {
int32 index = (int32)(addr_t)indices.ItemAtFast(i) - i;
delete RemoveItem(index);
}
}
void
DragSortableListView::GetSelectedItems(BList& indices)
{
for (int32 i = 0; true; i++) {
int32 index = CurrentSelection(i);
if (index < 0)
break;
if (!indices.AddItem((void*)(addr_t)index))
break;
}
}
void
DragSortableListView::RemoveSelected()
{
BList indices;
GetSelectedItems(indices);
int32 index = CurrentSelection() - 1;
DeselectAll();
if (indices.CountItems() > 0)
RemoveItemList(indices);
if (CountItems() > 0) {
if (index < 0)
index = 0;
Select(index);
}
}
void
DragSortableListView::RemoveAll()
{
BList indices;
int32 count = CountItems();
for (int32 i = 0; i < count; i++) {
if (!indices.AddItem((void*)(addr_t)i))
break;
}
if (indices.CountItems() > 0)
RemoveItemList(indices);
}
int32
DragSortableListView::CountSelectedItems() const
{
int32 count = 0;
while (CurrentSelection(count) >= 0)
count++;
return count;
}
void
DragSortableListView::SelectAll()
{
Select(0, CountItems() - 1);
}
bool
DragSortableListView::DeleteItem(int32 index)
{
BListItem* item = ItemAt(index);
if (item && RemoveItem(item)) {
delete item;
return true;
}
return false;
}
void
DragSortableListView::_SetDropAnticipationRect(BRect r)
{
if (fDropRect != r) {
if (fDropRect.IsValid())
Invalidate(fDropRect);
fDropRect = r;
if (fDropRect.IsValid())
Invalidate(fDropRect);
}
}
void
DragSortableListView::_SetDropIndex(int32 index)
{
if (fDropIndex != index) {
fDropIndex = index;
if (fDropIndex >= 0) {
int32 count = CountItems();
if (fDropIndex == count) {
BRect r;
if (ItemAt(count - 1)) {
r = ItemFrame(count - 1);
r.top = r.bottom;
r.bottom = r.top + 1.0;
} else {
r = Bounds();
r.bottom--;
}
_SetDropAnticipationRect(r);
} else {
BRect r = ItemFrame(fDropIndex);
r.top--;
r.bottom = r.top + 1.0;
_SetDropAnticipationRect(r);
}
}
}
}
void
DragSortableListView::_RemoveDropAnticipationRect()
{
_SetDropAnticipationRect(BRect(0.0, 0.0, -1.0, -1.0));
_SetDropIndex(-1);
}
void
DragSortableListView::_SetDragMessage(const BMessage* message)
{
if (message)
fDragMessageCopy = *message;
else
fDragMessageCopy.what = 0;
}
SimpleListView::SimpleListView(BRect frame, BMessage* selectionChangeMessage)
: DragSortableListView(frame, "playlist listview",
B_MULTIPLE_SELECTION_LIST, B_FOLLOW_ALL,
B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS),
fSelectionChangeMessage(selectionChangeMessage)
{
}
SimpleListView::SimpleListView(BRect frame, const char* name,
BMessage* selectionChangeMessage,
list_view_type type,
uint32 resizingMode, uint32 flags)
: DragSortableListView(frame, name, type, resizingMode, flags),
fSelectionChangeMessage(selectionChangeMessage)
{
}
SimpleListView::~SimpleListView()
{
delete fSelectionChangeMessage;
}
void
SimpleListView::MessageReceived( BMessage* message)
{
switch (message->what) {
default:
DragSortableListView::MessageReceived(message);
break;
}
}
void
SimpleListView::SelectionChanged()
{
BLooper* looper = Looper();
if (fSelectionChangeMessage && looper) {
BMessage message(*fSelectionChangeMessage);
looper->PostMessage(&message);
}
}
BListItem*
SimpleListView::CloneItem(int32 atIndex) const
{
BListItem* clone = NULL;
if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(atIndex)))
clone = new SimpleItem(item->Text());
return clone;
}
void
SimpleListView::DrawListItem(BView* owner, int32 index, BRect frame) const
{
if (SimpleItem* item = dynamic_cast<SimpleItem*>(ItemAt(index))) {
uint32 flags = FLAGS_NONE;
if (index == fFocusedIndex)
flags |= FLAGS_FOCUSED;
if (index % 2)
flags |= FLAGS_TINTED_LINE;
item->Draw(owner, frame, flags);
}
}
void
SimpleListView::MakeDragMessage(BMessage* message) const
{
if (message) {
message->AddPointer( "list", (void*)dynamic_cast<const DragSortableListView*>(this));
int32 index;
for (int32 i = 0; (index = CurrentSelection(i)) >= 0; i++)
message->AddInt32( "index", index );
}
}