/* * Copyright 2006-2007, Haiku. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus */ #include #include #include #include #include #include #include #include #include #include #include #include #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 class SimpleItem::SimpleItem( const char *name ) : BStringItem( name ) { } SimpleItem::~SimpleItem() { } // SimpleItem::DrawItem void SimpleItem::Draw(BView *owner, BRect frame, uint32 flags) { DrawBackground(owner, frame, flags); // label 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); } // SimpleItem::DrawBackground void SimpleItem::DrawBackground(BView *owner, BRect frame, uint32 flags) { // stroke a blue frame around the item if it's focused if (flags & FLAGS_FOCUSED) { owner->SetLowColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); owner->StrokeRect(frame, B_SOLID_LOW); frame.InsetBy(1.0, 1.0); } // figure out bg-color 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); // background owner->SetLowColor(color); owner->FillRect(frame, B_SOLID_LOW); } // DragSortableListView class 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; } // AttachedToWindow void DragSortableListView::AttachedToWindow() { BListView::AttachedToWindow(); // work arround a bug in BListView BRect bounds = Bounds(); BListView::FrameResized(bounds.Width(), bounds.Height()); } // DetachedFromWindow void DragSortableListView::DetachedFromWindow() { } // FrameResized 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); } } */ // Draw void DragSortableListView::Draw( BRect updateRect ) { int32 firstIndex = IndexOf(updateRect.LeftTop()); int32 lastIndex = IndexOf(updateRect.RightBottom()); if (firstIndex >= 0) { if (lastIndex < firstIndex) lastIndex = CountItems() - 1; // update rect contains items 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); } // drop anticipation indication if (fDropRect.IsValid()) { SetHighColor(255, 0, 0, 255); StrokeRect(fDropRect); } /* // focus indication if (IsFocus()) { SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); StrokeRect(Bounds()); }*/ } // ScrollTo 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); } // TargetedByScrollView void DragSortableListView::TargetedByScrollView(BScrollView* scrollView) { fScrollView = scrollView; BListView::TargetedByScrollView(scrollView); } // InitiateDrag bool DragSortableListView::InitiateDrag( BPoint point, int32 index, bool ) { // supress drag&drop while an item is focused if (fFocusedIndex >= 0) return false; bool success = false; BListItem* item = ItemAt( CurrentSelection( 0 ) ); if ( !item ) { // workarround a timing problem Select( index ); item = ItemAt( index ); } if ( item ) { // create drag message BMessage msg( fDragCommand ); MakeDragMessage( &msg ); // figure out drag rect float width = Bounds().Width(); BRect dragRect(0.0, 0.0, width, -1.0); // figure out, how many items fit into our bitmap 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; // let all selected items, that fit into our drag_bitmap, draw 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; } // make a black frame arround the edge 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; } // WindowActivated void DragSortableListView::WindowActivated( bool active ) { // workarround for buggy focus indication of BScrollView if ( BView* view = Parent() ) view->Invalidate(); } // MessageReceived 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)) { // calculate the vertical scrolling offset 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; } // scroll 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) { // update mouse position 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; } } } // KeyDown void DragSortableListView::KeyDown( const char* bytes, int32 numBytes ) { if ( numBytes < 1 ) return; if ( ( bytes[0] == B_BACKSPACE ) || ( bytes[0] == B_DELETE ) ) RemoveSelected(); BListView::KeyDown( bytes, numBytes ); } // MouseDown 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) { // only do something if user clicked the same item twice if (fLastClickedItem == item) DoubleClicked(i); } else { // remember last clicked item 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); } } // MouseMoved void DragSortableListView::MouseMoved(BPoint where, uint32 transit, const BMessage *msg) { if (msg && AcceptDragMessage(msg)) { switch (transit) { case B_ENTERED_VIEW: case B_INSIDE_VIEW: { // remember drag message // this is needed to react on modifier changes _SetDragMessage(msg); // set drop target through virtual function SetDropTargetRect(msg, where); // go into autoscrolling mode BRect r = Bounds(); r.InsetBy(0.0, r.Height() * SCROLL_AREA); SetAutoScrolling(!r.Contains(where)); break; } case B_EXITED_VIEW: // forget drag message _SetDragMessage(NULL); SetAutoScrolling(false); // fall through 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; } // MouseUp void DragSortableListView::MouseUp( BPoint where ) { // remove drop mark _SetDropAnticipationRect( BRect( 0.0, 0.0, -1.0, -1.0 ) ); SetAutoScrolling(false); // be sure to forget drag message _SetDragMessage(NULL); BListView::MouseUp( where ); BCursor cursor(B_HAND_CURSOR); SetViewCursor(&cursor, true); } // DrawItem void DragSortableListView::DrawItem( BListItem *item, BRect itemFrame, bool complete ) { DrawListItem( this, IndexOf( item ), itemFrame ); /* if (IsFocus()) { SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); StrokeRect(Bounds()); }*/ } // MouseWheelChanged bool DragSortableListView::MouseWheelChanged(float x, float y) { BPoint where; uint32 buttons; GetMouse(&where, &buttons, false); if (Bounds().Contains(where)) return true; else return false; } // SetDragCommand void DragSortableListView::SetDragCommand(uint32 command) { fDragCommand = command; } // ModifiersChaned void DragSortableListView::ModifiersChanged() { SetDropTargetRect(&fDragMessageCopy, fLastMousePos); } // SetItemFocused void DragSortableListView::SetItemFocused(int32 index) { InvalidateItem(fFocusedIndex); InvalidateItem(index); fFocusedIndex = index; } // AcceptDragMessage bool DragSortableListView::AcceptDragMessage(const BMessage* message) const { return message->what == fDragCommand; } // SetDropTargetRect void DragSortableListView::SetDropTargetRect(const BMessage* message, BPoint where) { if (AcceptDragMessage(message)) { bool copy = modifiers() & B_SHIFT_KEY; bool replaceAll = !message->HasPointer("list") && copy; // when dragging something in from ie Tracker, the // user has to hold shift down to replace everything // (opposite meaning of the normal shift behaviour) BRect r = Bounds(); if (replaceAll) { r.bottom--; // compensate for scrollbar offset _SetDropAnticipationRect(r); fDropIndex = -1; } else { // offset where by half of item height 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); } } } // SetAutoScrolling 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; } } // DoesAutoScrolling bool DragSortableListView::DoesAutoScrolling() const { return fScrollPulse; } // ScrollTo 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())); } } } // MoveItems void DragSortableListView::MoveItems(const BList& indices, int32 index) { DeselectAll(); // we remove the items while we look at them, the insertion index is decreased // when the items index is lower, so that we insert at the right spot after // removal 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--; } // else ??? -> blow up } count = removedItems.CountItems(); for (int32 i = 0; i < count; i++) { BListItem* item = (BListItem*)removedItems.ItemAtFast(i); if (AddItem(item, index)) { // after we're done, the newly inserted items will be selected Select(index, true); // next items will be inserted after this one index++; } else delete item; } } // CopyItems void DragSortableListView::CopyItems(const BList& indices, int32 toIndex) { DeselectAll(); // by inserting the items after we copied all items first, we avoid // cloning an item we already inserted and messing everything up // in other words, don't touch the list before we know which items // need to be cloned 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)) { // after we're done, the newly inserted items will be selected Select(toIndex, true); // next items will be inserted after this one toIndex++; } else delete item; } } // RemoveItemList 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); } } // GetSelectedItems 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; } } // RemoveSelected 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); } } // RemoveAll 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); } // CountSelectedItems int32 DragSortableListView::CountSelectedItems() const { int32 count = 0; while (CurrentSelection(count) >= 0) count++; return count; } // SelectAll void DragSortableListView::SelectAll() { Select(0, CountItems() - 1); } // DeleteItem bool DragSortableListView::DeleteItem(int32 index) { BListItem* item = ItemAt(index); if (item && RemoveItem(item)) { delete item; return true; } return false; } // _SetDropAnticipationRect void DragSortableListView::_SetDropAnticipationRect(BRect r) { if (fDropRect != r) { if (fDropRect.IsValid()) Invalidate(fDropRect); fDropRect = r; if (fDropRect.IsValid()) Invalidate(fDropRect); } } // _SetDropIndex 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--; // compensate for scrollbars moved slightly out of window } _SetDropAnticipationRect(r); } else { BRect r = ItemFrame(fDropIndex); r.top--; r.bottom = r.top + 1.0; _SetDropAnticipationRect(r); } } } } // _RemoveDropAnticipationRect void DragSortableListView::_RemoveDropAnticipationRect() { _SetDropAnticipationRect(BRect(0.0, 0.0, -1.0, -1.0)); _SetDropIndex(-1); } // _SetDragMessage void DragSortableListView::_SetDragMessage(const BMessage* message) { if (message) fDragMessageCopy = *message; else fDragMessageCopy.what = 0; } // SimpleListView class 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 class 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) { } // destructor SimpleListView::~SimpleListView() { delete fSelectionChangeMessage; } // MessageReceived void SimpleListView::MessageReceived( BMessage* message) { switch (message->what) { default: DragSortableListView::MessageReceived(message); break; } } // SelectionChanged void SimpleListView::SelectionChanged() { BLooper* looper = Looper(); if (fSelectionChangeMessage && looper) { BMessage message(*fSelectionChangeMessage); looper->PostMessage(&message); } } // CloneItem BListItem* SimpleListView::CloneItem(int32 atIndex) const { BListItem* clone = NULL; if (SimpleItem* item = dynamic_cast(ItemAt(atIndex))) clone = new SimpleItem(item->Text()); return clone; } // DrawListItem void SimpleListView::DrawListItem(BView* owner, int32 index, BRect frame) const { if (SimpleItem* item = dynamic_cast(ItemAt(index))) { uint32 flags = FLAGS_NONE; if (index == fFocusedIndex) flags |= FLAGS_FOCUSED; if (index % 2) flags |= FLAGS_TINTED_LINE; item->Draw(owner, frame, flags); } } // MakeDragMessage void SimpleListView::MakeDragMessage(BMessage* message) const { if (message) { message->AddPointer( "list", (void*)dynamic_cast(this)); int32 index; for (int32 i = 0; (index = CurrentSelection(i)) >= 0; i++) message->AddInt32( "index", index ); } }