#include "WINGsP.h"
#include <math.h> /* for : double rint (double) */



typedef struct W_Browser {
    W_Class widgetClass;
    W_View *view;

    char **titles;
    WMList **columns;

    short columnCount;
    short usedColumnCount;	       /* columns actually being used */
    short minColumnWidth;

    short maxVisibleColumns;
    short firstVisibleColumn;

    short titleHeight;

    short selectedColumn;

    WMSize columnSize;


    void *clientData;
    WMAction *action;
    void *doubleClientData;
    WMAction *doubleAction;

    WMBrowserDelegate *delegate;

    WMScroller *scroller;

    char *pathSeparator;

    struct {
        unsigned int isTitled:1;
        unsigned int allowMultipleSelection:1;
        unsigned int allowEmptySelection:1;
        unsigned int hasScroller:1;

        /* */
        unsigned int loaded:1;
        unsigned int loadingColumn:1;
    } flags;
} Browser;


#define COLUMN_SPACING 	4
#define TITLE_SPACING 2

#define DEFAULT_WIDTH                 305
#define DEFAULT_HEIGHT                200
#define DEFAULT_HAS_SCROLLER          True
#define DEFAULT_TITLE_HEIGHT          20
#define DEFAULT_IS_TITLED             True
#define DEFAULT_MAX_VISIBLE_COLUMNS   2
#define DEFAULT_SEPARATOR             "/"

#define MIN_VISIBLE_COLUMNS           1
#define MAX_VISIBLE_COLUMNS           32


#define COLUMN_IS_VISIBLE(b, c)	((c) >= (b)->firstVisibleColumn \
    && (c) < (b)->firstVisibleColumn + (b)->maxVisibleColumns)


static void handleEvents(XEvent *event, void *data);
static void destroyBrowser(WMBrowser *bPtr);

static void setupScroller(WMBrowser *bPtr);

static void scrollToColumn(WMBrowser *bPtr, int column, Bool updateScroller);

static void paintItem(WMList *lPtr, int index, Drawable d, char *text,
                      int state, WMRect *rect);

static void loadColumn(WMBrowser *bPtr, int column);

static void removeColumn(WMBrowser *bPtr, int column);

static char* createTruncatedString(WMFont *font, char *text, int *textLen,
                                   int width);

static void willResizeBrowser(W_ViewDelegate*, WMView*,
                              unsigned int*, unsigned int*);

W_ViewDelegate _BrowserViewDelegate = {
    NULL,
    NULL,
    NULL,
    NULL,
    willResizeBrowser
};



WMBrowser*
WMCreateBrowser(WMWidget *parent)
{
    WMBrowser *bPtr;
    int i;

    wassertrv(parent, NULL);

    bPtr = wmalloc(sizeof(WMBrowser));
    memset(bPtr, 0, sizeof(WMBrowser));

    bPtr->widgetClass = WC_Browser;

    bPtr->view = W_CreateView(W_VIEW(parent));
    if (!bPtr->view) {
        wfree(bPtr);
        return NULL;
    }
    bPtr->view->self = bPtr;

    bPtr->view->delegate = &_BrowserViewDelegate;

    WMCreateEventHandler(bPtr->view, ExposureMask|StructureNotifyMask
                         |ClientMessageMask, handleEvents, bPtr);

    /* default configuration */
    bPtr->flags.hasScroller = DEFAULT_HAS_SCROLLER;

    bPtr->titleHeight = DEFAULT_TITLE_HEIGHT;
    bPtr->flags.isTitled = DEFAULT_IS_TITLED;
    bPtr->maxVisibleColumns = DEFAULT_MAX_VISIBLE_COLUMNS;

    WMResizeWidget(bPtr, DEFAULT_WIDTH, DEFAULT_HEIGHT);

    bPtr->pathSeparator = wstrdup(DEFAULT_SEPARATOR);

    if (bPtr->flags.hasScroller)
        setupScroller(bPtr);

    for (i=0; i<bPtr->maxVisibleColumns; i++) {
        WMAddBrowserColumn(bPtr);
    }
    bPtr->usedColumnCount = 0;

    bPtr->selectedColumn = -1;

    return bPtr;
}


void
WMSetBrowserAllowMultipleSelection(WMBrowser *bPtr, Bool flag)
{
    int i;

    bPtr->flags.allowMultipleSelection = ((flag==0) ? 0 : 1);
    for (i=0; i<bPtr->columnCount; i++) {
        WMSetListAllowMultipleSelection(bPtr->columns[i], flag);
    }
}


void
WMSetBrowserAllowEmptySelection(WMBrowser *bPtr, Bool flag)
{
    int i;

    bPtr->flags.allowEmptySelection = ((flag==0) ? 0 : 1);
    for (i=0; i<bPtr->columnCount; i++) {
        WMSetListAllowEmptySelection(bPtr->columns[i], flag);
    }
}


int
WMGetBrowserMaxVisibleColumns(WMBrowser *bPtr)
{
    return bPtr->maxVisibleColumns;
}


void
WMSetBrowserMaxVisibleColumns(WMBrowser *bPtr, int columns)
{
    int curMaxVisibleColumns;
    int newFirstVisibleColumn = 0;

    assert ((int) bPtr);

    columns = (columns < MIN_VISIBLE_COLUMNS) ? MIN_VISIBLE_COLUMNS : columns;
    columns = (columns > MAX_VISIBLE_COLUMNS) ? MAX_VISIBLE_COLUMNS : columns;
    if (columns == bPtr->maxVisibleColumns) {
        return;
    }
    curMaxVisibleColumns = bPtr->maxVisibleColumns;
    bPtr->maxVisibleColumns = columns;
    /* browser not loaded */
    if (!bPtr->flags.loaded) {
        if ((columns > curMaxVisibleColumns) && (columns > bPtr->columnCount)) {
            int i = columns - bPtr->columnCount;
            bPtr->usedColumnCount = bPtr->columnCount;
            while (i--) {
                WMAddBrowserColumn(bPtr);
            }
            bPtr->usedColumnCount = 0;
        }
        /* browser loaded and columns > curMaxVisibleColumns */
    } else if (columns > curMaxVisibleColumns) {
        if (bPtr->usedColumnCount > columns) {
            newFirstVisibleColumn = bPtr->usedColumnCount - columns;
        }
        if (newFirstVisibleColumn > bPtr->firstVisibleColumn) {
            newFirstVisibleColumn = bPtr->firstVisibleColumn;
        }
        if (columns > bPtr->columnCount) {
            int i = columns - bPtr->columnCount;
            int curUsedColumnCount = bPtr->usedColumnCount;
            bPtr->usedColumnCount = bPtr->columnCount;
            while (i--) {
                WMAddBrowserColumn(bPtr);
            }
            bPtr->usedColumnCount = curUsedColumnCount;
        }
        /* browser loaded and columns < curMaxVisibleColumns */
    } else {
        newFirstVisibleColumn = bPtr->firstVisibleColumn;
        if (newFirstVisibleColumn + columns >= bPtr->usedColumnCount) {
            removeColumn(bPtr, newFirstVisibleColumn + columns);
        }
    }
    WMResizeWidget(bPtr, bPtr->view->size.width, bPtr->view->size.height);
    if (bPtr->flags.loaded) {
        XClearArea(bPtr->view->screen->display, bPtr->view->window, 0, 0,
                   bPtr->view->size.width, bPtr->titleHeight, False);
        scrollToColumn (bPtr, newFirstVisibleColumn, True);
    }
}


int
WMGetBrowserNumberOfColumns(WMBrowser *bPtr)
{
    return bPtr->usedColumnCount;
}

void
WMSetBrowserPathSeparator(WMBrowser *bPtr, char *separator)
{
    if (bPtr->pathSeparator)
        wfree(bPtr->pathSeparator);
    bPtr->pathSeparator = wstrdup(separator);
}



static void
drawTitleOfColumn(WMBrowser *bPtr, int column)
{
    WMScreen *scr = bPtr->view->screen;
    int x;

    x=(column-bPtr->firstVisibleColumn)*(bPtr->columnSize.width+COLUMN_SPACING);

    XFillRectangle(scr->display, bPtr->view->window, WMColorGC(scr->darkGray), x, 0,
                   bPtr->columnSize.width, bPtr->titleHeight);
    W_DrawRelief(scr, bPtr->view->window, x, 0,
                 bPtr->columnSize.width, bPtr->titleHeight, WRSunken);

    if (column < bPtr->usedColumnCount && bPtr->titles[column]) {
        int titleLen = strlen(bPtr->titles[column]);
        int widthC = bPtr->columnSize.width-8;

        if (WMWidthOfString(scr->boldFont, bPtr->titles[column], titleLen)
            > widthC) {
            char *titleBuf = createTruncatedString(scr->boldFont,
                                                   bPtr->titles[column],
                                                   &titleLen, widthC);
            W_PaintText(bPtr->view, bPtr->view->window, scr->boldFont, x,
                        (bPtr->titleHeight-WMFontHeight(scr->boldFont))/2,
                        bPtr->columnSize.width, WACenter, scr->white,
                        False, titleBuf, titleLen);
            wfree (titleBuf);
        } else {
            W_PaintText(bPtr->view, bPtr->view->window, scr->boldFont, x,
                        (bPtr->titleHeight-WMFontHeight(scr->boldFont))/2,
                        bPtr->columnSize.width, WACenter, scr->white,
                        False, bPtr->titles[column], titleLen);
        }
    }
}


WMList*
WMGetBrowserListInColumn(WMBrowser *bPtr, int column)
{
    if (column < 0 || column >= bPtr->usedColumnCount)
        return NULL;

    return bPtr->columns[column];
}


void
WMSetBrowserDelegate(WMBrowser *bPtr, WMBrowserDelegate *delegate)
{
    bPtr->delegate = delegate;
}


int
WMGetBrowserFirstVisibleColumn(WMBrowser *bPtr)
{
    return bPtr->firstVisibleColumn;
}


static void
removeColumn(WMBrowser *bPtr, int column)
{
    int i, clearEnd, destroyEnd;
    WMList **clist;
    char **tlist;

    assert ((int) bPtr);

    column = (column < 0) ? 0 : column;
    if (column >= bPtr->columnCount) {
        return;
    }
    if (column < bPtr->maxVisibleColumns) {
        clearEnd = bPtr->maxVisibleColumns;
        destroyEnd = bPtr->columnCount;
        bPtr->columnCount = bPtr->maxVisibleColumns;
    } else {
        clearEnd = column;
        destroyEnd = bPtr->columnCount;
        bPtr->columnCount = column;
    }
    if (column < bPtr->usedColumnCount) {
        bPtr->usedColumnCount = column;
    }
    for (i=column; i < clearEnd; i++) {
        if (bPtr->titles[i]) {
            wfree(bPtr->titles[i]);
            bPtr->titles[i] = NULL;
        }
        WMClearList(bPtr->columns[i]);
    }
    for (;i < destroyEnd; i++) {
        if (bPtr->titles[i]) {
            wfree(bPtr->titles[i]);
            bPtr->titles[i] = NULL;
        }
        WMRemoveNotificationObserverWithName(bPtr,
                                             WMListSelectionDidChangeNotification,
                                             bPtr->columns[i]);
        WMDestroyWidget(bPtr->columns[i]);
        bPtr->columns[i] = NULL;
    }
    clist = wmalloc(sizeof(WMList*) * (bPtr->columnCount));
    tlist = wmalloc(sizeof(char*) * (bPtr->columnCount));
    memcpy(clist, bPtr->columns, sizeof(WMList*) * (bPtr->columnCount));
    memcpy(tlist, bPtr->titles, sizeof(char*) * (bPtr->columnCount));
    wfree(bPtr->titles);
    wfree(bPtr->columns);
    bPtr->titles = tlist;
    bPtr->columns = clist;
}


WMListItem*
WMGetBrowserSelectedItemInColumn(WMBrowser *bPtr, int column)
{
    if ((column < 0) || (column >= bPtr->usedColumnCount))
        return NULL;

    return WMGetListSelectedItem(bPtr->columns[column]);
}



int
WMGetBrowserSelectedColumn(WMBrowser *bPtr)
{
    return bPtr->selectedColumn;
}


int
WMGetBrowserSelectedRowInColumn(WMBrowser *bPtr, int column)
{
    if (column >= 0 && column < bPtr->columnCount) {
        return WMGetListSelectedItemRow(bPtr->columns[column]);
    } else {
        return -1;
    }
}


void
WMSetBrowserColumnTitle(WMBrowser *bPtr, int column, char *title)
{
    assert(column >= 0);
    assert(column < bPtr->usedColumnCount);

    if (bPtr->titles[column])
        wfree(bPtr->titles[column]);

    bPtr->titles[column] = wstrdup(title);

    if (COLUMN_IS_VISIBLE(bPtr, column) && bPtr->flags.isTitled) {
        drawTitleOfColumn(bPtr, column);
    }
}


void
WMSetBrowserTitled(WMBrowser *bPtr, Bool flag)
{
    int i;
    int columnX, columnY;

    flag = ((flag==0) ? 0 : 1);

    if (bPtr->flags.isTitled == flag)
        return;

    columnX = 0;

    if (!bPtr->flags.isTitled) {
        columnY = TITLE_SPACING + bPtr->titleHeight;

        bPtr->columnSize.height -= columnY;

        for (i=0; i<bPtr->columnCount; i++) {
            WMResizeWidget(bPtr->columns[i], bPtr->columnSize.width,
                           bPtr->columnSize.height);

            columnX = WMWidgetView(bPtr->columns[i])->pos.x;

            WMMoveWidget(bPtr->columns[i], columnX, columnY);
        }
    } else {
        bPtr->columnSize.height += TITLE_SPACING + bPtr->titleHeight;

        for (i=0; i<bPtr->columnCount; i++) {
            WMResizeWidget(bPtr->columns[i], bPtr->columnSize.width,
                           bPtr->columnSize.height);

            columnX = WMWidgetView(bPtr->columns[i])->pos.x;

            WMMoveWidget(bPtr->columns[i], columnX, 0);
        }
    }

    bPtr->flags.isTitled = flag;
}


void
WMSortBrowserColumn(WMBrowser *bPtr, int column)
{
    WMSortListItems(bPtr->columns[column]);
}


void
WMSortBrowserColumnWithComparer(WMBrowser *bPtr, int column,
                                WMCompareDataProc *func)
{
    WMSortListItemsWithComparer(bPtr->columns[column], func);
}



WMListItem*
WMInsertBrowserItem(WMBrowser *bPtr, int column, int row, char *text,
                    Bool isBranch)
{
    WMListItem *item;

    if (column < 0 || column >= bPtr->columnCount)
        return NULL;

    item = WMInsertListItem(bPtr->columns[column], row, text);
    item->isBranch = isBranch;

    return item;
}




static void
willResizeBrowser(W_ViewDelegate *self, WMView *view,
                  unsigned int *width, unsigned int *height)
{
    WMBrowser *bPtr = (WMBrowser*)view->self;
    int cols = bPtr->maxVisibleColumns;
    int colX, colY;
    int i;

    assert(*width > 0);
    assert(*height > 0);

    bPtr->columnSize.width = (*width-(cols-1)*COLUMN_SPACING) / cols;
    bPtr->columnSize.height = *height;

    if (bPtr->flags.isTitled) {
        colY = TITLE_SPACING + bPtr->titleHeight;
        bPtr->columnSize.height -= colY;
    } else {
        colY = 0;
    }

    if (bPtr->flags.hasScroller) {
        bPtr->columnSize.height -= SCROLLER_WIDTH + 4;

        if (bPtr->scroller) {
            WMResizeWidget(bPtr->scroller, *width-2, 1);
            WMMoveWidget(bPtr->scroller, 1, *height-SCROLLER_WIDTH-1);
        }
    }

    colX = 0;
    for (i = 0; i < bPtr->columnCount; i++) {
        WMResizeWidget(bPtr->columns[i], bPtr->columnSize.width,
                       bPtr->columnSize.height);

        WMMoveWidget(bPtr->columns[i], colX, colY);

        if (COLUMN_IS_VISIBLE(bPtr, i)) {
            colX += bPtr->columnSize.width+COLUMN_SPACING;
        }
    }
}


static void
paintItem(WMList *lPtr, int index, Drawable d, char *text, int state, WMRect *rect)
{
    WMView *view = W_VIEW(lPtr);
    W_Screen *scr = view->screen;
    Display *display = scr->display;
    WMFont *font = ((state & WLDSIsBranch) ? scr->boldFont : scr->normalFont);
    WMColor *backColor = ((state & WLDSSelected) ? scr->white : view->backColor);
    int width, height, x, y, textLen;

    width = rect->size.width;
    height = rect->size.height;
    x = rect->pos.x;
    y = rect->pos.y;
    textLen = strlen(text);

    XFillRectangle(display, d, WMColorGC(backColor), x, y, width, height);

    if (text) {
        /* Avoid overlaping... */
        int widthC = (state & WLDSIsBranch) ? width-20 : width-8;
        if (WMWidthOfString(font, text, textLen) > widthC) {
            char *textBuf = createTruncatedString(font, text, &textLen, widthC);
            W_PaintText(view, d, font,  x+4, y, widthC,
                        WALeft, scr->black, False, textBuf, textLen);
            wfree(textBuf);
        } else {
            W_PaintText(view, d, font,  x+4, y, widthC,
                        WALeft, scr->black, False, text, textLen);
        }
    }

    if (state & WLDSIsBranch) {
        WMColor *lineColor = ((state & WLDSSelected) ? scr->gray : scr->white);

        XDrawLine(display, d, WMColorGC(scr->darkGray), x+width-11, y+3,
                  x+width-6, y+height/2);
        XDrawLine(display, d, WMColorGC(lineColor), x+width-11, y+height-5,
                  x+width-6, y+height/2);
        XDrawLine(display, d, WMColorGC(scr->black), x+width-12, y+3,
                  x+width-12, y+height-5);
    }
}


static void
scrollCallback(WMWidget *scroller, void *self)
{
    WMBrowser *bPtr = (WMBrowser*)self;
    WMScroller *sPtr = (WMScroller*)scroller;
    int newFirst;
#define LAST_VISIBLE_COLUMN  bPtr->firstVisibleColumn+bPtr->maxVisibleColumns

    switch (WMGetScrollerHitPart(sPtr)) {
    case WSDecrementLine:
        if (bPtr->firstVisibleColumn > 0) {
            scrollToColumn(bPtr, bPtr->firstVisibleColumn-1, True);
        }
        break;

    case WSDecrementPage:
    case WSDecrementWheel:
        if (bPtr->firstVisibleColumn > 0) {
            newFirst = bPtr->firstVisibleColumn - bPtr->maxVisibleColumns;

            scrollToColumn(bPtr, newFirst, True);
        }
        break;


    case WSIncrementLine:
        if (LAST_VISIBLE_COLUMN < bPtr->usedColumnCount) {
            scrollToColumn(bPtr, bPtr->firstVisibleColumn+1, True);
        }
        break;

    case WSIncrementPage:
    case WSIncrementWheel:
        if (LAST_VISIBLE_COLUMN < bPtr->usedColumnCount) {
            newFirst = bPtr->firstVisibleColumn + bPtr->maxVisibleColumns;

            if (newFirst+bPtr->maxVisibleColumns >= bPtr->columnCount)
                newFirst = bPtr->columnCount - bPtr->maxVisibleColumns;

            scrollToColumn(bPtr, newFirst, True);
        }
        break;

    case WSKnob:
        {
            double floatValue;
            double value = bPtr->columnCount - bPtr->maxVisibleColumns;

            floatValue = WMGetScrollerValue(bPtr->scroller);

            floatValue = (floatValue*value)/value;

            newFirst = rint(floatValue*(float)(bPtr->columnCount - bPtr->maxVisibleColumns));

            if (bPtr->firstVisibleColumn != newFirst)
                scrollToColumn(bPtr, newFirst, False);
            /*else
             WMSetScrollerParameters(bPtr->scroller, floatValue,
             bPtr->maxVisibleColumns/(float)bPtr->columnCount);
             */

        }
        break;

    case WSKnobSlot:
    case WSNoPart:
        /* do nothing */
        break;
    }
#undef LAST_VISIBLE_COLUMN
}


static void
setupScroller(WMBrowser *bPtr)
{
    WMScroller *sPtr;
    int y;

    y = bPtr->view->size.height - SCROLLER_WIDTH - 1;

    sPtr = WMCreateScroller(bPtr);
    WMSetScrollerAction(sPtr, scrollCallback, bPtr);
    WMMoveWidget(sPtr, 1, y);
    WMResizeWidget(sPtr, bPtr->view->size.width-2, SCROLLER_WIDTH);

    bPtr->scroller = sPtr;

    WMMapWidget(sPtr);
}


void
WMSetBrowserAction(WMBrowser *bPtr, WMAction *action, void *clientData)
{
    bPtr->action = action;
    bPtr->clientData = clientData;
}


void
WMSetBrowserDoubleAction(WMBrowser *bPtr, WMAction *action, void *clientData)
{
    bPtr->doubleAction = action;
    bPtr->doubleClientData = clientData;
}


void
WMSetBrowserHasScroller(WMBrowser *bPtr, int hasScroller)
{
    bPtr->flags.hasScroller = hasScroller;
}


char*
WMSetBrowserPath(WMBrowser *bPtr, char *path)
{
    int i;
    char *str;
    char *tmp, *retPtr = NULL;
    int item;
    WMListItem *listItem;

    /* WMLoadBrowserColumnZero must be call first */
    if (!bPtr->flags.loaded) {
        return False;
    }

    removeColumn(bPtr, 1);

    WMSelectListItem(bPtr->columns[0], -1);
    WMSetListPosition(bPtr->columns[0], 0);

    i = 0;
    str = wstrdup(path);
    tmp = strtok(str, bPtr->pathSeparator);
    while (tmp) {
        /* select it in the column */
        item = WMFindRowOfListItemWithTitle(bPtr->columns[i], tmp);
        if (item<0) {
            retPtr = &path[(int)(tmp - str)];
            break;
        }
        WMSelectListItem(bPtr->columns[i], item);
        WMSetListPosition(bPtr->columns[i], item);

        listItem = WMGetListItem(bPtr->columns[i], item);
        if (!listItem || !listItem->isBranch) {
            break;
        }

        /* load next column */
        WMAddBrowserColumn(bPtr);

        loadColumn(bPtr, i+1);

        tmp = strtok(NULL, bPtr->pathSeparator);

        i++;
    }

    wfree(str);

    for (i = bPtr->usedColumnCount - 1;
         (i > -1) && !WMGetListSelectedItem(bPtr->columns[i]);
         i--);

    bPtr->selectedColumn = i;

    if (bPtr->columnCount < bPtr->maxVisibleColumns) {
        int i = bPtr->maxVisibleColumns - bPtr->columnCount;
        int curUsedColumnCount = bPtr->usedColumnCount;
        bPtr->usedColumnCount = bPtr->columnCount;
        while (i--) {
            WMAddBrowserColumn(bPtr);
        }
        bPtr->usedColumnCount = curUsedColumnCount;
    }

    scrollToColumn(bPtr, bPtr->columnCount - bPtr->maxVisibleColumns, True);

    return retPtr;
}


char*
WMGetBrowserPath(WMBrowser *bPtr)
{
    return WMGetBrowserPathToColumn(bPtr, bPtr->columnCount);
}


char*
WMGetBrowserPathToColumn(WMBrowser *bPtr, int column)
{
    int i, size;
    char *path;
    WMListItem *item;

    if (column >= bPtr->usedColumnCount)
        column = bPtr->usedColumnCount-1;

    if (column < 0) {
        return wstrdup(bPtr->pathSeparator);
    }

    /* calculate size of buffer */
    size = 0;
    for (i = 0; i <= column; i++) {
        item = WMGetListSelectedItem(bPtr->columns[i]);
        if (!item)
            break;
        size += strlen(item->text);
    }

    /* get the path */
    path = wmalloc(size+(column+1)*strlen(bPtr->pathSeparator)+1);
    /* ignore first / */
    *path = 0;
    for (i = 0; i <= column; i++) {
        strcat(path, bPtr->pathSeparator);
        item = WMGetListSelectedItem(bPtr->columns[i]);
        if (!item)
            break;
        strcat(path, item->text);
    }

    return path;
}


WMArray*
WMGetBrowserPaths(WMBrowser *bPtr)
{
    int column, i, k, size, selNo;
    char *path;
    WMListItem *item, *lastItem;
    WMArray *paths, *items;

    column = bPtr->usedColumnCount-1;

    if (column < 0) {
        paths = WMCreateArrayWithDestructor(1, wfree);
        WMAddToArray(paths, wstrdup(bPtr->pathSeparator));
        return paths;
    }

    items = WMGetListSelectedItems(bPtr->columns[column]);
    selNo = WMGetArrayItemCount(items);
    paths = WMCreateArrayWithDestructor(selNo, wfree);

    if (selNo <= 1) {
        WMAddToArray(paths, WMGetBrowserPath(bPtr));
        return paths;
    }

    /* calculate size of buffer */
    size = 0;
    for (i=0; i<column; i++) {
        item = WMGetListSelectedItem(bPtr->columns[i]);
        if (!item)
            break;
        size += strlen(item->text);
    }

    size += (column+1)*strlen(bPtr->pathSeparator)+1;

    for (k=0; k<selNo; k++) {
        /* get the path */
        lastItem = WMGetFromArray(items, k);
        path = wmalloc(size + (lastItem!=NULL ?  strlen(lastItem->text) : 0));
        /* ignore first / */
        *path = 0;
        for (i=0; i<=column; i++) {
            strcat(path, bPtr->pathSeparator);
            if (i == column) {
                item = lastItem;
            } else {
                item = WMGetListSelectedItem(bPtr->columns[i]);
            }
            if (!item)
                break;
            strcat(path, item->text);
        }
        WMAddToArray(paths, path);
    }

    return paths;
}


Bool
WMBrowserAllowsMultipleSelection(WMBrowser *bPtr)
{
    return bPtr->flags.allowMultipleSelection;
}


Bool
WMBrowserAllowsEmptySelection(WMBrowser *bPtr)
{
    return bPtr->flags.allowEmptySelection;
}


static void
loadColumn(WMBrowser *bPtr, int column)
{
    assert(bPtr->delegate);
    assert(bPtr->delegate->createRowsForColumn);

    bPtr->flags.loadingColumn = 1;
    (*bPtr->delegate->createRowsForColumn)(bPtr->delegate, bPtr, column,
                                           bPtr->columns[column]);
    bPtr->flags.loadingColumn = 0;

    if (bPtr->delegate->titleOfColumn) {
        char *title;

        title = (*bPtr->delegate->titleOfColumn)(bPtr->delegate, bPtr, column);

        if (bPtr->titles[column])
            wfree(bPtr->titles[column]);

        bPtr->titles[column] = wstrdup(title);

        if (COLUMN_IS_VISIBLE(bPtr, column) && bPtr->flags.isTitled) {
            drawTitleOfColumn(bPtr, column);
        }
    }
}


static void
paintBrowser(WMBrowser *bPtr)
{
    int i;

    if (!bPtr->view->flags.mapped)
        return;

    W_DrawRelief(bPtr->view->screen, bPtr->view->window, 0,
                 bPtr->view->size.height-SCROLLER_WIDTH-2,
                 bPtr->view->size.width, 22, WRSunken);

    if (bPtr->flags.isTitled) {
        for (i=0; i<bPtr->maxVisibleColumns; i++) {
            drawTitleOfColumn(bPtr, i+bPtr->firstVisibleColumn);
        }
    }
}


static void
handleEvents(XEvent *event, void *data)
{
    WMBrowser *bPtr = (WMBrowser*)data;

    CHECK_CLASS(data, WC_Browser);


    switch (event->type) {
    case Expose:
        paintBrowser(bPtr);
        break;

    case DestroyNotify:
        destroyBrowser(bPtr);
        break;

    }
}



static void
scrollToColumn(WMBrowser *bPtr, int column, Bool updateScroller)
{
    int i;
    int x;
    int notify = 0;


    if (column != bPtr->firstVisibleColumn) {
        notify = 1;
    }

    if (column < 0)
        column = 0;

    if (notify && bPtr->delegate && bPtr->delegate->willScroll) {
        (*bPtr->delegate->willScroll)(bPtr->delegate, bPtr);
    }

    x = 0;
    bPtr->firstVisibleColumn = column;
    for (i = 0; i < bPtr->columnCount; i++) {
        if (COLUMN_IS_VISIBLE(bPtr, i)) {
            WMMoveWidget(bPtr->columns[i], x,
                         WMWidgetView(bPtr->columns[i])->pos.y);
            if (!WMWidgetView(bPtr->columns[i])->flags.realized)
                WMRealizeWidget(bPtr->columns[i]);
            WMMapWidget(bPtr->columns[i]);
            x += bPtr->columnSize.width + COLUMN_SPACING;
        } else {
            WMUnmapWidget(bPtr->columns[i]);
        }
    }

    /* update the scroller */
    if (updateScroller) {
        if (bPtr->columnCount > bPtr->maxVisibleColumns) {
            float value, proportion;

            value = bPtr->firstVisibleColumn
                /(float)(bPtr->columnCount-bPtr->maxVisibleColumns);
            proportion = bPtr->maxVisibleColumns/(float)bPtr->columnCount;
            WMSetScrollerParameters(bPtr->scroller, value, proportion);
        } else {
            WMSetScrollerParameters(bPtr->scroller, 0, 1);
        }
    }

    if (bPtr->view->flags.mapped)
        paintBrowser(bPtr);

    if (notify && bPtr->delegate && bPtr->delegate->didScroll) {
        (*bPtr->delegate->didScroll)(bPtr->delegate, bPtr);
    }
}


static void
listCallback(void *self, void *clientData)
{
    WMBrowser *bPtr = (WMBrowser*)clientData;
    WMList *lPtr = (WMList*)self;
    WMListItem *item;
    int i, selNo;
    static WMListItem *oldItem = NULL;
    static int oldSelNo = 0;

    item = WMGetListSelectedItem(lPtr);
    selNo = WMGetArrayItemCount(WMGetListSelectedItems(lPtr));

    if (oldItem==NULL || oldItem!=item || oldSelNo!=selNo) {
        for (i=0; i<bPtr->columnCount; i++) {
            if (lPtr == bPtr->columns[i])
                break;
        }
        assert(i<bPtr->columnCount);

        bPtr->selectedColumn = i;

        /* columns at right must be cleared */
        removeColumn(bPtr, i+1);
        /* open directory */
        if (item && item->isBranch && selNo==1) {
            WMAddBrowserColumn(bPtr);
        }
        if (bPtr->usedColumnCount < bPtr->maxVisibleColumns)
            i = 0;
        else
            i = bPtr->usedColumnCount-bPtr->maxVisibleColumns;
        scrollToColumn(bPtr, i, True);
        if (item && item->isBranch && selNo==1) {
            loadColumn(bPtr, bPtr->usedColumnCount-1);
        }
    }

    /* call callback for click */
    if (bPtr->action)
        (*bPtr->action)(bPtr, bPtr->clientData);

    oldItem = item;
    oldSelNo = selNo;
}


static void
listDoubleCallback(void *self, void *clientData)
{
    WMBrowser *bPtr = (WMBrowser*)clientData;
    WMList *lPtr = (WMList*)self;
    WMListItem *item;

    item = WMGetListSelectedItem(lPtr);
    if (!item)
        return;

    /* call callback for double click */
    if (bPtr->doubleAction)
        (*bPtr->doubleAction)(bPtr, bPtr->doubleClientData);
}


void
WMLoadBrowserColumnZero(WMBrowser *bPtr)
{
    if (!bPtr->flags.loaded) {
        /* create column 0 */
        WMAddBrowserColumn(bPtr);

        loadColumn(bPtr, 0);

        /* make column 0 visible */
        scrollToColumn(bPtr, 0, True);

        bPtr->flags.loaded = 1;
    }
}


void
WMRemoveBrowserItem(WMBrowser *bPtr, int column, int row)
{
    WMList *list;

    if (column < 0 || column >= bPtr->usedColumnCount)
        return;

    list = WMGetBrowserListInColumn(bPtr, column);

    if (row < 0 || row >= WMGetListNumberOfRows(list))
        return;

    removeColumn(bPtr, column+1);
    if (bPtr->usedColumnCount < bPtr->maxVisibleColumns)
        scrollToColumn(bPtr, 0, True);
    else
        scrollToColumn(bPtr, bPtr->usedColumnCount-bPtr->maxVisibleColumns,
                       True);

    WMRemoveListItem(list, row);
}


static void
listSelectionObserver(void *observerData, WMNotification *notification)
{
    WMBrowser *bPtr = (WMBrowser*)observerData;
    int column;
    WMList *lPtr = (WMList*)WMGetNotificationObject(notification);

    for (column = 0; column < bPtr->usedColumnCount; column++)
        if (bPtr->columns[column] == lPtr)
            break;

    /* this can happen when a list is being cleared with WMClearList
     * after the column was removed */
    if (column >= bPtr->usedColumnCount) {
        return;
    }

    if (WMGetArrayItemCount(WMGetListSelectedItems(lPtr)) == 0)
        column--;

    bPtr->selectedColumn = column;
}


int
WMAddBrowserColumn(WMBrowser *bPtr)
{
    WMList *list;
    WMList **clist;
    char **tlist;
    int colY;
    int index;


    if (bPtr->usedColumnCount < bPtr->columnCount) {
        return bPtr->usedColumnCount++;
    }

    bPtr->usedColumnCount++;

    if (bPtr->flags.isTitled) {
        colY = TITLE_SPACING + bPtr->titleHeight;
    } else {
        colY = 0;
    }

    index = bPtr->columnCount;
    bPtr->columnCount++;
    clist = wmalloc(sizeof(WMList*)*bPtr->columnCount);
    tlist = wmalloc(sizeof(char*)*bPtr->columnCount);
    memcpy(clist, bPtr->columns, sizeof(WMList*)*(bPtr->columnCount-1));
    memcpy(tlist, bPtr->titles, sizeof(char*)*(bPtr->columnCount-1));
    if (bPtr->columns)
        wfree(bPtr->columns);
    if (bPtr->titles)
        wfree(bPtr->titles);
    bPtr->columns = clist;
    bPtr->titles = tlist;

    bPtr->titles[index] = NULL;

    list = WMCreateList(bPtr);
    WMSetListAllowMultipleSelection(list, bPtr->flags.allowMultipleSelection);
    WMSetListAllowEmptySelection(list, bPtr->flags.allowEmptySelection);
    WMSetListAction(list, listCallback, bPtr);
    WMSetListDoubleAction(list, listDoubleCallback, bPtr);
    WMSetListUserDrawProc(list, paintItem);
    WMAddNotificationObserver(listSelectionObserver, bPtr,
                              WMListSelectionDidChangeNotification, list);

    bPtr->columns[index] = list;

    WMResizeWidget(list, bPtr->columnSize.width, bPtr->columnSize.height);
    WMMoveWidget(list, (bPtr->columnSize.width+COLUMN_SPACING)*index, colY);
    if (COLUMN_IS_VISIBLE(bPtr, index))
        WMMapWidget(list);

    /* update the scroller */
    if (bPtr->columnCount > bPtr->maxVisibleColumns) {
        float value, proportion;

        value = bPtr->firstVisibleColumn
            /(float)(bPtr->columnCount-bPtr->maxVisibleColumns);
        proportion = bPtr->maxVisibleColumns/(float)bPtr->columnCount;
        WMSetScrollerParameters(bPtr->scroller, value, proportion);
    }

    return index;
}



static void
destroyBrowser(WMBrowser *bPtr)
{
    int i;

    for (i = 0; i < bPtr->columnCount; i++) {
        if (bPtr->titles[i])
            wfree(bPtr->titles[i]);
    }
    wfree(bPtr->titles);

    wfree(bPtr->pathSeparator);

    WMRemoveNotificationObserver(bPtr);

    wfree(bPtr);
}


static char*
createTruncatedString(WMFont *font, char *text, int *textLen, int width)
{
    int dLen = WMWidthOfString(font, ".", 1);
    char *textBuf = (char*)wmalloc((*textLen)+4);

    if (width >= 3*dLen) {
        int dddLen = 3*dLen;
        int tmpTextLen = *textLen;

        strcpy(textBuf, text);
        while (tmpTextLen
               && (WMWidthOfString(font, textBuf, tmpTextLen)+dddLen > width))
            tmpTextLen--;
        strcpy(textBuf+tmpTextLen, "...");
        *textLen = tmpTextLen+3;
    } else if (width >= 2*dLen) {
        strcpy(textBuf, "..");
        *textLen = 2;
    } else if (width >= dLen) {
        strcpy(textBuf, ".");
        *textLen = 1;
    } else {
        *textBuf = '\0';
        *textLen = 0;
    }
    return textBuf;
}



syntax highlighted by Code2HTML, v. 0.9.1