root/src/apps/icon-o-matic/generic/gui/ListViews.cpp
/*
 * Copyright 2006, 2023, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stephan Aßmus <superstippi@gmx.de>
 *              Zardshard
 */

#include "ListViews.h"

#include <malloc.h>
#include <stdio.h>
#include <typeinfo>

#include <Bitmap.h>
#include <Clipboard.h>
#include <Cursor.h>
#include <Entry.h>
#include <MessageRunner.h>
#include <Messenger.h>
#include <ScrollBar.h>
#include <ScrollView.h>
#include <StackOrHeapArray.h>
#include <String.h>
#include <Window.h>

#include "cursors.h"

#include "Selection.h"

#define MAX_DRAG_HEIGHT         200.0
#define ALPHA                           170
#define TEXT_OFFSET                     5.0


static const rgb_color kDropIndicatorColor = make_color(255, 65, 54, 255);
static const rgb_color kDragFrameColor = make_color(17, 17, 17, 255);


// #pragma mark - SimpleItem


SimpleItem::SimpleItem(const char *name)
        :
        BStringItem(name)
{
}


SimpleItem::~SimpleItem()
{
}


void
SimpleItem::DrawItem(BView* owner, BRect itemFrame, bool even)
{
        DrawBackground(owner, itemFrame, even);

        // label
        if (IsSelected())
                owner->SetHighUIColor(B_LIST_SELECTED_ITEM_TEXT_COLOR);
        else
                owner->SetHighUIColor(B_LIST_ITEM_TEXT_COLOR);

        font_height fh;
        owner->GetFontHeight(&fh);

        const char* text = Text();
        BString truncatedString(text);
        owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE,
                itemFrame.Width() - TEXT_OFFSET - 4);

        float height = itemFrame.Height();
        float textHeight = fh.ascent + fh.descent;
        BPoint textPoint;
        textPoint.x = itemFrame.left + TEXT_OFFSET;
        textPoint.y = itemFrame.top
                + ceilf(height / 2 - textHeight / 2 + fh.ascent);

        owner->DrawString(truncatedString.String(), textPoint);
}


void
SimpleItem::DrawBackground(BView* owner, BRect itemFrame, bool even)
{
        rgb_color bgColor;
        if (!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);
                bgColor = disabledColor;
        } else if (IsSelected())
                bgColor = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
        else
                bgColor = ui_color(B_LIST_BACKGROUND_COLOR);

        if (even)
                bgColor = tint_color(bgColor, 1.06);

        owner->SetLowColor(bgColor);
        owner->FillRect(itemFrame, B_SOLID_LOW);
}


// #pragma mark - DragSortableListView


DragSortableListView::DragSortableListView(BRect frame, const char* name,
        list_view_type type, uint32 resizingMode, uint32 flags)
        :
        BListView(frame, name, type, resizingMode, flags),
        fDropRect(0, 0, -1, -1),
        fMouseWheelFilter(NULL),
        fScrollPulse(NULL),
        fDropIndex(-1),
        fLastClickedItem(NULL),
        fScrollView(NULL),
        fDragCommand(B_SIMPLE_DATA),
        fFocusedIndex(-1),
        fSelection(NULL),
        fSyncingToSelection(false),
        fModifyingSelection(false)
{
        SetViewColor(B_TRANSPARENT_32_BIT);
}


DragSortableListView::~DragSortableListView()
{
        delete fMouseWheelFilter;

        SetSelection(NULL);
}


void
DragSortableListView::AttachedToWindow()
{
        if (!fMouseWheelFilter)
                fMouseWheelFilter = new MouseWheelFilter(this);
        Window()->AddCommonFilter(fMouseWheelFilter);

        BListView::AttachedToWindow();

        // work arround a bug in BListView
        BRect bounds = Bounds();
        BListView::FrameResized(bounds.Width(), bounds.Height());
}


void
DragSortableListView::DetachedFromWindow()
{
        Window()->RemoveCommonFilter(fMouseWheelFilter);
}


void
DragSortableListView::FrameResized(float width, float height)
{
        BListView::FrameResized(width, height);
}


void
DragSortableListView::TargetedByScrollView(BScrollView* scrollView)
{
        fScrollView = scrollView;
        BListView::TargetedByScrollView(scrollView);
}


void
DragSortableListView::WindowActivated(bool active)
{
        // work-around for buggy focus indicator on BScrollView
        if (BView* view = Parent())
                view->Invalidate();
}


void
DragSortableListView::MessageReceived(BMessage* message)
{
        if (message->what == fDragCommand) {
                int32 count = CountItems();
                if (fDropIndex < 0 || fDropIndex > count)
                        fDropIndex = count;
                HandleDropMessage(message, fDropIndex);
                fDropIndex = -1;
        } else {
                switch (message->what) {
                        case B_MOUSE_WHEEL_CHANGED:
                        {
                                BListView::MessageReceived(message);
                                BPoint where;
                                uint32 buttons;
                                GetMouse(&where, &buttons, false);
                                uint32 transit = Bounds().Contains(where) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
                                MouseMoved(where, 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) || (bytes[0] == B_DELETE))
                RemoveSelected();

        BListView::KeyDown(bytes, numBytes);
}


void
DragSortableListView::MouseDown(BPoint where)
{
        int32 index = IndexOf(where);
        BListItem* item = ItemAt(index);

        // bail out if item not found
        if (index < 0 || item == NULL) {
                fLastClickedItem = NULL;
                return BListView::MouseDown(where);
        }

        int32 clicks = 1;
        int32 buttons = 0;
        Window()->CurrentMessage()->FindInt32("clicks", &clicks);
        Window()->CurrentMessage()->FindInt32("buttons", &buttons);

        if (clicks == 2 && item == fLastClickedItem) {
                // only do something if user clicked the same item twice
                DoubleClicked(index);
        } else {
                // remember last clicked item
                fLastClickedItem = item;
        }

        if (ListType() == B_MULTIPLE_SELECTION_LIST
                && (buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
                if (item->IsSelected())
                        Deselect(index);
                else
                        Select(index, true);
        } else
                BListView::MouseDown(where);
}


void
DragSortableListView::MouseMoved(BPoint where, uint32 transit, const BMessage* msg)
{
        int32 buttons = 0;
        Window()->CurrentMessage()->FindInt32("buttons", &buttons);

        // only start a drag if a button is down and we have a drag message
        if (buttons > 0 && msg && AcceptDragMessage(msg)) {
                // we have dragged off the mouse down item
                // turn on auto-scrolling and drag and drop
                switch (transit) {
                        case B_ENTERED_VIEW:
                        case B_INSIDE_VIEW:
                                // remember drag message to react on modifier changes
                                SetDragMessage(msg);
                                // set drop target through virtual function
                                SetDropTargetRect(msg, where);
                        break;

                        case B_EXITED_VIEW:
                                // forget drag message
                                SetDragMessage(NULL);
                                // don't draw drop rect indicator
                                InvalidateDropRect();
                        case B_OUTSIDE_VIEW:
                                break;
                }
        } else {
                // be sure to forget drag message
                SetDragMessage(NULL);
                // don't draw drop rect indicator
                InvalidateDropRect();
                // restore hand cursor
                BCursor cursor(B_HAND_CURSOR);
                SetViewCursor(&cursor, true);
        }

        fLastMousePos = where;
        BListView::MouseMoved(where, transit, msg);
}


void
DragSortableListView::MouseUp(BPoint where)
{
        // turn off auto-scrolling
        BListView::MouseUp(where);
        // be sure to forget drag message
        SetDragMessage(NULL);
        // don't draw drop rect indicator
        InvalidateDropRect();
        // restore hand cursor
        BCursor cursor(B_HAND_CURSOR);
        SetViewCursor(&cursor, true);
}


bool
DragSortableListView::MouseWheelChanged(float x, float y)
{
        BPoint where;
        uint32 buttons;
        GetMouse(&where, &buttons, false);
        if (Bounds().Contains(where))
                return true;
        else
                return false;
}


// #pragma mark -


void
DragSortableListView::ObjectChanged(const Observable* object)
{
        if (object != fSelection || fModifyingSelection || fSyncingToSelection)
                return;

//printf("%s - syncing start\n", Name());
        fSyncingToSelection = true;

        // try to sync to Selection
        BList selectedItems;

        int32 count = fSelection->CountSelected();
        for (int32 i = 0; i < count; i++) {
                int32 index = IndexOfSelectable(fSelection->SelectableAtFast(i));
                if (index >= 0) {
                        BListItem* item = ItemAt(index);
                        if (item && !selectedItems.HasItem((void*)item))
                                selectedItems.AddItem((void*)item);
                }
        }

        count = selectedItems.CountItems();
        if (count == 0) {
                if (CurrentSelection(0) >= 0)
                        DeselectAll();
        } else {
                count = CountItems();
                for (int32 i = 0; i < count; i++) {
                        BListItem* item = ItemAt(i);
                        bool selected = selectedItems.RemoveItem((void*)item);
                        if (item->IsSelected() != selected) {
                                Select(i, true);
                        }
                }
        }

        fSyncingToSelection = false;
//printf("%s - done\n", Name());
}


// #pragma mark -


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 rect = Bounds();
                if (replaceAll) {
                        // compensate for scrollbar offset
                        rect.bottom--;
                        fDropRect = rect;
                        fDropIndex = -1;
                } else {
                        // offset where by half of item height
                        rect = ItemFrame(0);
                        where.y += rect.Height() / 2;

                        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);
                }
        }
}


bool
DragSortableListView::HandleDropMessage(const BMessage* message,
        int32 dropIndex)
{
        DragSortableListView *list = NULL;
        if (message->FindPointer("list", (void **)&list) != B_OK || list != this)
                return false;

        BList items;
        int32 index;
        for (int32 i = 0; message->FindInt32("index", i, &index) == B_OK; i++) {
                BListItem* item = ItemAt(index);
                if (item != NULL)
                        items.AddItem((void*)item);
        }

        if (items.CountItems() == 0)
                return false;

        if ((modifiers() & B_SHIFT_KEY) != 0)
                CopyItems(items, dropIndex);
        else
                MoveItems(items, dropIndex);

        return true;
}


bool
DragSortableListView::DoesAutoScrolling() const
{
        return true;
}


void
DragSortableListView::MoveItems(BList& items, 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 = items.CountItems();
        for (int32 i = 0; i < count; i++) {
                BListItem* item = (BListItem*)items.ItemAt(i);
                int32 removeIndex = IndexOf(item);
                if (RemoveItem(item) && removedItems.AddItem((void*)item)) {
                        if (removeIndex < index)
                                index--;
                }
                // else ??? -> blow up
        }
        for (int32 i = 0;
                 BListItem* item = (BListItem*)removedItems.ItemAt(i); 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;
        }
}


void
DragSortableListView::CopyItems(BList& items, int32 index)
{
        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 = items.CountItems();
        for (int32 i = 0; i < count; i++) {
                BListItem* item = CloneItem(IndexOf((BListItem*)items.ItemAt(i)));
                if (item && !clonedItems.AddItem((void*)item))
                        delete item;
        }
        for (int32 i = 0;
                 BListItem* item = (BListItem*)clonedItems.ItemAt(i); 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;
        }
}


void
DragSortableListView::RemoveItemList(BList& items)
{
        int32 count = items.CountItems();
        for (int32 i = 0; i < count; i++) {
                BListItem* item = (BListItem*)items.ItemAt(i);
                if (RemoveItem(item))
                        delete item;
        }
}


void
DragSortableListView::RemoveSelected()
{
//      if (fFocusedIndex >= 0)
//              return;

        BList items;
        for (int32 i = 0; BListItem* item = ItemAt(CurrentSelection(i)); i++)
                items.AddItem((void*)item);
        RemoveItemList(items);
}


// #pragma mark -


void
DragSortableListView::SetSelection(Selection* selection)
{
        if (fSelection == selection)
                return;

        if (fSelection)
                fSelection->RemoveObserver(this);

        fSelection = selection;

        if (fSelection)
                fSelection->AddObserver(this);
}


int32
DragSortableListView::IndexOfSelectable(Selectable* selectable) const
{
        return -1;
}


Selectable*
DragSortableListView::SelectableFor(BListItem* item) const
{
        return NULL;
}


void
DragSortableListView::SelectAll()
{
        Select(0, CountItems() - 1);
}


int32
DragSortableListView::CountSelectedItems() const
{
        int32 count = 0;
        while (CurrentSelection(count) >= 0)
                count++;
        return count;
}


void
DragSortableListView::SelectionChanged()
{
//printf("%s::SelectionChanged()", typeid(*this).name());
        // modify global Selection
        if (!fSelection || fSyncingToSelection)
                return;

        fModifyingSelection = true;

        BList selectables;
        for (int32 i = 0; BListItem* item = ItemAt(CurrentSelection(i)); i++) {
                Selectable* selectable = SelectableFor(item);
                if (selectable)
                        selectables.AddItem((void*)selectable);
        }

        AutoNotificationSuspender _(fSelection);

        int32 count = selectables.CountItems();
        if (count == 0) {
//printf("  deselecting all\n");
                if (!fSyncingToSelection)
                        fSelection->DeselectAll();
        } else {
//printf("  selecting %ld items\n", count);
                for (int32 i = 0; i < count; i++) {
                        Selectable* selectable = (Selectable*)selectables.ItemAtFast(i);
                        fSelection->Select(selectable, i > 0);
                }
        }

        fModifyingSelection = false;
}


// #pragma mark -


bool
DragSortableListView::DeleteItem(int32 index)
{
        BListItem* item = ItemAt(index);
        if (item && RemoveItem(item)) {
                delete item;
                return true;
        }
        return false;
}


void
DragSortableListView::SetDropRect(BRect rect)
{
        fDropRect = rect;
}


void
DragSortableListView::SetDropIndex(int32 index)
{
        if (fDropIndex != index) {
                fDropIndex = index;
                if (fDropIndex >= 0) {
                        int32 count = CountItems();
                        if (fDropIndex == count) {
                                BRect rect;
                                if (ItemAt(count - 1)) {
                                        rect = ItemFrame(count - 1);
                                        rect.top = rect.bottom;
                                        rect.bottom = rect.top + 1;
                                } else {
                                        rect = Bounds();
                                        // compensate for scrollbars moved slightly out of window
                                        rect.bottom--;
                                }
                                fDropRect = rect;
                        } else {
                                BRect rect = ItemFrame(fDropIndex);
                                rect.top--;
                                rect.bottom = rect.top + 1;
                                fDropRect = rect;
                        }
                }
        }
}


void
DragSortableListView::InvalidateDropRect()
{
        fDropRect = BRect(0, 0, -1, -1);
//      SetDropIndex(-1);
}


void
DragSortableListView::SetDragMessage(const BMessage* message)
{
        if (message)
                fDragMessageCopy = *message;
        else
                fDragMessageCopy.what = 0;
}


// #pragma mark - SimpleListView


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 | B_FULL_UPDATE_ON_RESIZE),
                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::DetachedFromWindow()
{
        DragSortableListView::DetachedFromWindow();
        _MakeEmpty();
}


void
SimpleListView::Draw(BRect updateRect)
{
        BRect emptyRect = updateRect;

        int32 firstIndex = IndexOf(updateRect.LeftTop());
        int32 lastIndex = IndexOf(updateRect.RightBottom());
        if (firstIndex >= 0) {
                BListItem* item;
                BRect itemFrame(0, 0, -1, -1);
                if (lastIndex < firstIndex)
                        lastIndex = CountItems() - 1;
                // update rect contains items
                for (int32 i = firstIndex; i <= lastIndex; i++) {
                        item = ItemAt(i);
                        if (item == NULL)
                                continue;
                        itemFrame = ItemFrame(i);
                        item->DrawItem(this, itemFrame, (i % 2) == 0);

                        // drop indicator
                        if (i == fDropIndex) {
                                SetHighColor(kDropIndicatorColor);
                                StrokeLine(fDropRect.LeftTop(), fDropRect.RightTop());
                        }
                }
                emptyRect.top = itemFrame.bottom + 1;
        }

        if (emptyRect.IsValid()) {
                SetLowUIColor(B_LIST_BACKGROUND_COLOR);
                FillRect(emptyRect, B_SOLID_LOW);
        }

#if 0
        // focus indicator
        if (IsFocus()) {
                SetHighUIColor(B_KEYBOARD_NAVIGATION_COLOR);
                StrokeRect(Bounds());
        }
#endif
}


bool
SimpleListView::InitiateDrag(BPoint where, int32 index, bool)
{
        // supress drag & drop while an item is focused
        if (fFocusedIndex >= 0)
                return false;

        BListItem* item = ItemAt(CurrentSelection(0));
        if (item == NULL) {
                // work-around a timing problem
                Select(index);
                item = ItemAt(index);
        }
        if (item == NULL)
                return false;

        // create drag message
        BMessage msg(fDragCommand);
        MakeDragMessage(&msg);
        // figure out drag rect
        float width = Bounds().Width();
        BRect dragRect(0, 0, width, -1);
        // 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;
                if (dragRect.Height() > MAX_DRAG_HEIGHT) {
                        fade = true;
                        dragRect.bottom = MAX_DRAG_HEIGHT;
                        numItems++;
                        break;
                }
        }

        BBitmap* dragBitmap = new BBitmap(dragRect, B_RGBA32, true);
        if (dragBitmap && dragBitmap->IsValid()) {
                if (BView* view = new BView(dragBitmap->Bounds(), "helper",
                                B_FOLLOW_NONE, B_WILL_DRAW)) {
                        dragBitmap->AddChild(view);
                        dragBitmap->Lock();
                        BRect itemFrame(dragRect) ;
                        itemFrame.bottom = 0.0;
                        BListItem* item;
                        // let all selected items, that fit into our drag_bitmap, draw
                        for (int32 i = 0; i < numItems; i++) {
                                item = ItemAt(CurrentSelection(i));
                                if (item == NULL)
                                        continue;
                                itemFrame.bottom = itemFrame.top + ceilf(item->Height());
                                if (itemFrame.bottom > dragRect.bottom)
                                        itemFrame.bottom = dragRect.bottom;
                                item->DrawItem(view, itemFrame, (i % 2) == 0);
                                itemFrame.top = itemFrame.bottom + 1;
                        }

                        // stroke a black frame around the edge
                        view->SetHighColor(kDragFrameColor);
                        view->StrokeRect(view->Bounds());
                        view->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, B_ORIGIN);
        else
                DragMessage(&msg, dragRect.OffsetToCopy(where), this);

        SetDragMessage(&msg);

        return true;
}


void
SimpleListView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                // NOTE: pasting is handled in MainWindow::MessageReceived
                case B_COPY:
                {
                        int count = CountSelectedItems();
                        if (count == 0)
                                return;

                        if (!be_clipboard->Lock())
                                break;
                        be_clipboard->Clear();

                        BMessage data;
                        ArchiveSelection(&data);

                        ssize_t size = data.FlattenedSize();
                        BStackOrHeapArray<char, 1024> archive(size);
                        if (!archive) {
                                be_clipboard->Unlock();
                                break;
                        }
                        data.Flatten(archive, size);

                        be_clipboard->Data()->AddData(
                                "application/x-vnd.icon_o_matic-listview-message", B_MIME_TYPE, archive, size);

                        be_clipboard->Commit();
                        be_clipboard->Unlock();

                        break;
                }

                default:
                        DragSortableListView::MessageReceived(message);
                        break;
        }
}


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::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);
        }

        BMessage items;
        ArchiveSelection(&items);
        message->AddMessage("items", &items);
}


bool
SimpleListView::HandleDropMessage(const BMessage* message, int32 dropIndex)
{
        // Let DragSortableListView handle drag-sorting (when drag came from ourself)
        if (DragSortableListView::HandleDropMessage(message, dropIndex))
                return true;

        BMessage items;
        if (message->FindMessage("items", &items) != B_OK)
                return false;

        return InstantiateSelection(&items, dropIndex);
}


bool
SimpleListView::HandlePaste(const BMessage* archive)
{
        return InstantiateSelection(archive, CountItems());
}


void
SimpleListView::_MakeEmpty()
{
        // NOTE: BListView::MakeEmpty() uses ScrollTo()
        // for which the object needs to be attached to
        // a BWindow.... :-(
        int32 count = CountItems();
        for (int32 i = count - 1; i >= 0; i--)
                delete RemoveItem(i);
}